Home / Alt manpages / docker-run(1)

  • docker-run(1)
  • User command
  • linux

Run Docker Containers with Predictable Lifecycles

Leave off --rm on a one-shot docker run and you will be tripping over stopped containers by the end of the week. This covers starting a container, choosing whether it is temporary or persistent, publishing a service safely, and cleaning up afterwards. Allow about fifteen minutes. The examples use Docker Engine CLI package docker-ce-cli version 5:29.8.1-1~ubuntu.24.04~noble on this machine.

You need a working Docker daemon and permission to reach its socket, which may mean prefixing commands with sudo depending on the local setup. None of the examples below need root inside the container, and adding sudo to every single command is not a substitute for configuring Docker access properly.

1. Confirm the command and daemon

Start with read-only checks. The command takes the form docker run [OPTIONS] IMAGE [COMMAND] [ARG...]. The image is required; a command and arguments, if supplied, override the image's own defaults.

$ docker version --format '{{.Client.Version}}'
29.8.1
$ docker run --help | sed -n '1,12p'

The installed client can pull an image when it is missing locally. The default pull policy is missing, so a first run can quietly need network access and change the local image cache. Use --pull=never when a test must not download anything at all.

Checkpoint

If docker version reports a daemon connection error, stop here. Check the Docker service and socket access with your operating system's normal administration tools before touching the container command.

2. Run a disposable command

For a one-shot job, combine --rm with a command that exits on its own. Docker removes the container, and its anonymous volumes, once the process ends:

$ docker run --rm --pull=missing hello-world
Hello from Docker!

Your own output will include the image's own message and may mention a different architecture or Docker version. What matters is exit status zero and no stopped container left behind by this example:

$ docker ps -a --filter ancestor=hello-world --format '{{.Names}} {{.Status}}'
$

An empty result is exactly what --rm is supposed to leave behind. Skip --rm if you need to inspect a failed container afterwards; instead, record the generated name or ID and remove the container once you have collected its logs.

3. Name a persistent container

A container stops when its main process exits. Detached mode, -d, runs that process in the background and prints the new container ID. Give a long-lived container an explicit name so later commands do not depend on a random one Docker generated for you:

$ docker run -d --name web-demo nginx:alpine
CONTAINER_ID
$ docker ps --filter name=web-demo
CONTAINER ID   IMAGE          COMMAND                  STATUS         PORTS     NAMES
CONTAINER_ID   nginx:alpine   "/docker-entrypoint...."   Up ...         80/tcp    web-demo

The exact ID, age and command formatting vary. Check the service without entering the container at all:

$ docker logs --tail 20 web-demo
$ docker inspect --format '{{.State.Status}}' web-demo
running

To stop it, ask the process to exit normally first, then remove the stopped container:

$ docker stop web-demo
web-demo
$ docker rm web-demo
web-demo

These commands change Docker state but leave the image untouched. Do not treat docker rm -f as a routine stop; it can terminate the process abruptly instead of letting it shut down.

4. Publish a port deliberately

An image's EXPOSE metadata does not publish anything on the host by itself. Use -p to create an explicit mapping, and bind a development service to loopback unless it genuinely needs to be reachable from other machines:

$ docker run -d --name web-demo -p 127.0.0.1:8080:80 nginx:alpine
CONTAINER_ID
$ docker port web-demo
80/tcp -> 127.0.0.1:8080

Visit http://127.0.0.1:8080/ from the Docker host, or just check the headers:

$ curl --fail --head http://127.0.0.1:8080/
HTTP/1.1 200 OK

Do not swap the address for 0.0.0.0 casually. Publishing without a host address normally binds every host interface, which can expose the service to a whole network. Docker's own firewall rules can also make a published port reachable in ways a plain host firewall rule would not expect.

5. Add storage and configuration

Use a named volume for data that needs to survive the container being replaced. Use --mount when the source and destination should be unambiguous in a reviewed command:

$ docker volume create demo-data
demo-data
$ docker run --rm --name data-check \
  --mount type=volume,source=demo-data,destination=/data \
  -e APP_MODE=check alpine:3.20 \
  sh -c 'printf "%s\n" "$APP_MODE" > /data/mode; cat /data/mode'
check

The volume survives even though the temporary container was removed. Verify it exists before relying on it for anything real:

$ docker volume inspect demo-data
[ ... ]

Security warning

Do not put passwords or API tokens directly in a shell command; they can end up in shell history and process listings. Prefer a proper secret-management mechanism, or an environment file with tightly controlled permissions, and remember that environment variables are still visible to any process with sufficient access and may get picked up by diagnostic tooling.

6. Choose interactive, read-only and privilege settings

Use -i to keep standard input open and -t to allocate a pseudo-terminal. Together they make a short-lived shell usable:

$ docker run --rm -it --name shell-demo alpine:3.20 sh
/ # printf 'container shell ready\n'
container shell ready
/ # exit

For an application container, consider these settings on purpose rather than as an afterthought:

  • --read-only with a writable named volume or temporary filesystem only where the application genuinely needs one.
  • --user once the image documents a suitable non-root account to run as.

Test these before deploying: they can expose an assumption an image was quietly relying on.

Warning

--privileged grants broad access to host devices and kernel features. It is not a normal fix for a permission error, and it weakens the container boundary considerably. Prefer the smallest necessary capability with --cap-add, or a specific device, and review the security impact before starting the container. A Docker socket mount is similarly powerful, because it can end up controlling the host daemon itself.

7. Diagnose and recover

If an image cannot be found, work out whether the name or the pull policy is the actual problem:

$ docker run --pull=never alpine:3.20 true
$ docker image inspect alpine:3.20 --format '{{.Id}}'

If the image is genuinely absent, rerun without --pull=never only when downloading from the configured registry is acceptable. Pin a tag or digest for repeatable deployments; an unqualified image name commonly implies a registry and the latest tag, which can move under you over time.

If a detached container exits unexpectedly, inspect its state and logs before removing it:

$ docker ps -a --filter name=web-demo
$ docker inspect --format 'status={{.State.Status}} exit={{.State.ExitCode}}' web-demo
$ docker logs web-demo

Once you have actually looked, recover the resources explicitly:

$ docker rm web-demo
$ docker volume rm demo-data

Destructive action

That last command permanently removes the named volume and its contents. Run it only when the data is disposable or already backed up. Images are a separate resource and are not touched by docker rm.

Done means

  • Checked client and daemon access before troubleshooting a container.
  • Used --rm for disposable work, or recorded the name of a persistent container.
  • Bound published development ports to loopback unless external access was intentional.
  • Kept durable data in a named volume and know the command that removes it.
  • Inspected logs and exit status before forcing a stop or deleting evidence.
  • Left out --privileged and the Docker socket mount without a specific, reviewed reason.