Run Docker Containers with Predictable Lifecycles
You will use docker run to start a container, choose whether it is temporary or persistent, publish a service safely, and clean up what you created. The examples use Docker Engine CLI package docker-ce-cli version 5:29.8.1-1~ubuntu.24.04~noble on this machine.
The route
Jump straight to the step you need, or tick off Done means at the end.
Allow about fifteen minutes. You need a working Docker daemon and permission to access its socket. Depending on the local Docker setup, that may mean prefixing commands with sudo. The examples below do not need root inside the container, and adding sudo to every command is not a substitute for configuring Docker access correctly.
1. Confirm the command and daemon
Start with read-only checks. The command has the form docker run [OPTIONS] IMAGE [COMMAND] [ARG...]. The image is required; the command and arguments override the image's defaults when supplied.
$ docker version --format '{{.Client.Version}}'
29.8.1
$ docker run --help | sed -n '1,12p'
The installed client may pull an image when it is missing locally. The default pull policy is missing, so a first run can require network access and can change the local image cache. Use --pull=never when a test must not download anything.
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 changing the container command.
2. Run a disposable command
For a one-shot job, combine --rm with a command that exits. Docker removes the container and its anonymous volumes after the process exits.
$ docker run --rm --pull=missing hello-world
Hello from Docker!
Your output will include the image's own message and may mention a different architecture or Docker version. The useful result is exit status zero and the absence of a stopped container left behind by this example:
$ docker ps -a --filter ancestor=hello-world --format '{{.Names}} {{.Status}}'
$
An empty result is expected after --rm. Do not use --rm if you need to inspect a failed container later; omit it, record the generated name or ID, and remove the container after collecting 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 Docker's random name.
$ 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 output without entering the container:
$ 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 not the image. Do not use docker rm -f as a routine stop: it can terminate the process abruptly.
4. Publish a port deliberately
An image's EXPOSE metadata does not publish a port on the host. Use -p to create an explicit mapping. Bind a development service to loopback unless it is meant 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 check only the headers:
$ curl --fail --head http://127.0.0.1:8080/
HTTP/1.1 200 OK
Do not replace the address with 0.0.0.0 casually. Publishing without a host address normally binds on all host interfaces, which can expose the service to a network. Docker's firewall rules can also make a published port reachable in ways that do not match a simple host firewall rule.
5. Add storage and configuration
Use a named volume for data that should survive container replacement. 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 remains after the temporary container is removed. Verify it exists before relying on it:
$ docker volume inspect demo-data
[ ... ]
Do not put passwords or API tokens directly in a shell command: they can enter shell history and process listings. Prefer a suitable secret-management mechanism or an environment file with tightly controlled permissions. Remember that environment variables are still visible to processes with sufficient access and may be recorded 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 are useful for a short-lived shell:
$ 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 --read-only and provide a writable named volume or temporary filesystem only where the application needs one. Use --user when the image documents a suitable non-root account. These settings can expose an image assumption that was previously hidden, so test them before deploying.
Warning: --privileged grants broad access to host devices and kernel features. It is not a normal fix for a permission error and weakens the container boundary. 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 control the host daemon.
7. Diagnose and recover
If an image cannot be found, decide whether the name or the pull policy is wrong:
$ docker run --pull=never alpine:3.20 true
$ docker image inspect alpine:3.20 --format '{{.Id}}'
If the image is 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 may change over time.
If a detached container exits, 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
After the investigation, recover the resources explicitly:
$ docker rm web-demo
$ docker volume rm demo-data
The final command permanently removes the named volume and its contents. Run it only when the data is disposable or has been backed up. Images are separate resources and are not removed by docker rm.
Done means
- You checked client and daemon access before troubleshooting a container.
- You used
--rmfor disposable work, or recorded the name of a persistent container. - You bound published development ports to loopback unless external access was intentional.
- You kept durable data in a named volume and know the command that removes it.
- You inspected logs and exit status before forcing a stop or deleting evidence.
- You did not add
--privilegedor mount the Docker socket without a specific, reviewed reason.