Run a Docker Container Safely with docker container run

docker container run is the command everyone reaches for first, and the one that quietly grants too much access if you copy a flag without reading it. This guide builds a repeatable, checked way to run a short-lived container, name it, pass a command, check the result and remove it cleanly.

Allow about fifteen minutes. You need a working Docker daemon, an image available locally or permission to pull one, and a shell. The examples match Docker Engine 29.8.1 and the docker-ce-cli 5:29.8.1-1~ubuntu.24.04~noble package installed on this machine. Most commands are ordinary user commands; do not add sudo automatically, since it changes which credentials and Docker configuration are used.

1. Check the client and daemon

Confirm the command version and that the client can reach its daemon. These checks do not create a container:

$ docker --version
Docker version 29.8.1, build 4a63305
$ docker info --format '{{.ServerVersion}}'
29.8.1

Checkpoint: Continue only once docker info prints a server version. The syntax is docker container run [OPTIONS] IMAGE [COMMAND] [ARG...]; docker run is its shorter alias.

2. Run a harmless, named test container

Use an image already present on the host when possible, to avoid an unexpected pull and keep the first test quick:

$ docker container run --rm --name run-smoke-test alpine:3 printf '%s\n' 'container-ok'
container-ok

The image reference comes before the command. Here Docker starts alpine:3, runs printf with its arguments, prints the result and exits. --rm removes the container automatically when it exits; the name is useful while it runs and prevents an accidental collision with another container.

Docker normally pulls a missing image, because --pull defaults to missing. Use --pull=never when a missing image should be an error, or --pull=always when a fresh registry check is required. A tag such as alpine:3 is more specific than an omitted tag, which normally means latest; a digest is stronger still when you need content pinning.

Check the exit status immediately after a test if the result matters:

$ docker container run --rm --name run-status-test alpine:3 sh -c 'exit 7'
$ printf 'exit status: %s\n' "$?"
exit status: 7

An exit status from the container command is returned by docker run for ordinary command failures. Docker uses 125 for a Docker or daemon-side failure, 126 when the requested command cannot be invoked, and 127 when it cannot be found. Do not confuse a successful image pull with a successful application run.

3. Choose foreground or background operation

Foreground mode is the default and suits a command whose output or completion you are watching. Add --detach, or -d, when the container should keep running after your shell prompt returns:

$ docker container run -d --name run-background-test alpine:3 sh -c 'sleep 20'
<container-id>
$ docker container ps --filter name=run-background-test
CONTAINER ID   IMAGE      COMMAND       STATUS          NAMES
<container-id> alpine:3  ...           Up ... seconds  run-background-test

The ID is variable. Inspect output with docker container logs run-background-test, or wait for completion with docker container wait run-background-test. Once the process has finished, remove this test container:

$ docker container rm run-background-test
run-background-test

Recovery: Removing a container is irreversible for data held only in its writable container layer. The command above is safe for this sleep-only test. For real work, preserve data in a named volume or a carefully chosen bind mount before removing the container.

4. Pass environment and a working directory

Options must appear before the image; the command and its arguments come after it. This example passes a non-secret setting and asks the image to print it from a known directory:

$ docker container run --rm --name run-options-test \
    --env APP_MODE=test \
    --workdir /tmp \
    alpine:3 sh -c 'printf "mode=%s\npwd=%s\n" "$APP_MODE" "$PWD"'
mode=test
pwd=/tmp

Do not put passwords, API tokens or private keys directly in a command that can end up in shell history or process logs. Use a suitable secret-management approach for sensitive values. An --env-file can keep ordinary configuration out of the command line, but its permissions and contents still need review.

5. Add storage only when you need it

A container's writable layer is temporary from an application's point of view; a volume persists independently of a container. This example creates a volume, writes one file, reads it from a second container, then removes both container and volume:

$ docker volume create run-demo-data
run-demo-data
$ docker container run --rm --mount source=run-demo-data,target=/data alpine:3 \
    sh -c 'printf "%s\n" saved > /data/result.txt'
$ docker container run --rm --mount source=run-demo-data,target=/data alpine:3 \
    cat /data/result.txt
saved
$ docker volume rm run-demo-data
run-demo-data

Warning: A volume removal destroys the data in that volume. The final command is an explicit cleanup for this disposable example, not a general post-run habit. For a persistent workload, leave it out and document who owns the volume and how it is backed up.

Bind mounts are different: they expose a host path inside the container and are read-write by default. Treat a bind-mounted path as host data. Prefer a read-only mount, such as --mount type=bind,source=/path/to/input,target=/input,readonly, when the container only needs to read it. Check the source path before starting, especially when a daemon runs on another host.

6. Review risky options before using them

Publishing a port with -p HOST_PORT:CONTAINER_PORT changes host reachability. Check the resulting mapping with docker container port NAME, bind to a deliberate host address when appropriate, and remove or stop the container when the service is not needed. -P publishes all image-declared ports to randomly chosen host ports, convenient for testing but easy to overlook.

Done means