Chapter 3: Running Containers
Topic 14

The Anatomy of docker run

CLIRun

docker run is the command you type more than any other, and almost every flag on it answers one question: what does this process get for a name, a terminal, a network port, a filesystem, an environment, and a lifetime. The shape is always docker run [options] IMAGE [command] [args] — flags before the image configure the container, and everything after the image overrides what the image runs. Learn the dozen flags that matter and the rest are lookup.

The order is not decoration. Put the application's command before the image name and Docker tries to read it as a flag and errors; put a flag after the image and it gets passed to your program as an argument. The split at the image name is the one piece of syntax worth memorizing before anything else.

Detached vs Foreground

By default run attaches to the container's stdout and stderr and blocks your terminal until the process exits. -d (detached) starts the container in the background and prints its ID, returning your shell immediately. Foreground is for interactive sessions and one-shot work; a long-lived service like the Driftwood web container runs detached, because a foreground process dies the moment you close the terminal it is attached to.

Foreground blocks the shell; detached returns it
$ docker run driftwood/web          # blocks — your terminal is the container's
[2024-01-15 10:22:01] gunicorn listening on :8000
^C                                  # Ctrl-C here stops the container

$ docker run -d --name driftwood-web driftwood/web
3f9a1c7e2b8d4a6f...                 # prints the ID, shell returns
$ docker logs -f driftwood-web      # follow its output from here instead

Naming and Identity

Without --name, Docker assigns a random two-word name like vibrant_hopper alongside the 64-character ID. --name driftwood-web gives the container a stable handle you can stop, logs, and exec against without copying a truncated ID each time. The name is also what other containers resolve over a user-defined network, so naming is not just convenience — it is how service discovery works once Driftwood has more than one container.

Interactivity — the -it Pair

-i keeps stdin open so the process can read input; -t allocates a pseudo-TTY so the session looks and behaves like a terminal. Together, -it gives you a usable interactive shell — docker run -it ubuntu bash drops you into a prompt inside the container. A non-interactive batch process needs neither, and adding -it to a background service out of habit is a common cause of "it hangs" or "the shell looks broken," because you have allocated a terminal for a process that has none.

Ports, Mounts, and Environment

Four flags wire the container into the world around it. -p 8000:8000 publishes a container port to the host. -v or --mount attaches a volume or bind mount. -e or --env-file sets environment variables. --network driftwood-net joins a user-defined network. Each of these has a full chapter of its own — ports and networks in Chapter 7, mounts in Chapter 6, environment in topic 19 — but docker run is where they all get wired up on a single container.

Running the Driftwood web container the way you actually would
$ docker run -d \
    --name driftwood-web \
    --network driftwood-net \
    -p 127.0.0.1:8000:8000 \
    -e DATABASE_URL=postgresql://db:5432/driftwood \
    --restart unless-stopped \
    driftwood/web

Overriding the Image's Command

Anything after the image name replaces the image's default CMD (and is passed as arguments to its ENTRYPOINT). docker run python:3.12 python -c "print(1)" runs your command instead of the image's default, which is how one image serves a long-running server, a one-off script, and a debugging shell depending on what you put after the name. The image defines a default; the command line overrides it.

What docker run actually does, step by step
pull if missing
create container
start process
attach / detach
Common Mistakes
  • Running the Driftwood web container without -d in a terminal you then close — the foreground process dies with the terminal, taking the service with it; long-lived services need detached mode.
  • Adding -it to a non-interactive background service out of habit — allocating a TTY for a process that has no terminal breaks log formatting and signal handling in subtle ways and masks whether the workload is actually interactive.
  • Forgetting -p and then being unable to reach web from the host — without a published port the container is reachable only from inside its Docker network, which surprises people who expect localhost:8000 to just work.
  • Leaving every container unnamed and then juggling random two-word names and truncated IDs — --name costs nothing and makes every later stop, logs, and exec legible.
  • Passing the application's command before the image name — flags go before the image, the command goes after; the order is not interchangeable, and getting it wrong is a parse error or a misrouted argument.
Best Practices
  • Run long-lived services detached and named (docker run -d --name driftwood-web …) so they survive your shell and stay addressable for stop, logs, and exec.
  • Publish only the ports the service needs with -p, and bind to a specific interface (-p 127.0.0.1:8000:8000) when the port should not be reachable from outside the host.
  • Pass configuration with -e/--env-file and data with -v/--mount rather than baking environment-specific values into the image, keeping one image runnable everywhere (topic 19 goes deep).
  • Reserve -it for genuinely interactive runs and one-off shells, not for background services, so TTY allocation matches the actual workload.
Comparable tools Podman run accepts the same flags nearly verbatim, a deliberate drop-in nerdctl run over containerd mirrors the syntax docker create is the same option set without the start step — the run flags are the OCI runtime config surface as a CLI

Knowledge Check

Why does a long-lived service like the Driftwood web container need -d?

  • Foreground mode ties the container to your terminal, so it dies when the terminal closes — -d runs it in the background
  • Detached mode makes the container automatically restart itself in the background, with no restart policy set, whenever its main process crashes or exits non-zero
  • Without -d the container's published ports stay bound internally and are never exposed to the host
  • A detached container runs measurably faster because it isn't streaming its output to a terminal

What do -i and -t each contribute, and when do you need both?

  • -i keeps stdin open, -t allocates a pseudo-TTY — together a usable interactive shell
  • -i runs the container detached in the background and -t times out and closes idle sessions
  • -i encrypts the stdin stream and -t opens an encrypted TLS tunnel into the container
  • You need -it on every container, even a detached background service, for signal handling to work

Why does the position of the command relative to the image name matter?

  • Flags before the image configure the container; everything after the image overrides what the image runs
  • The order is purely stylistic — Docker parses the flags and the command from any position on the line
  • A command placed before the image name runs as root, while a command placed after it runs unprivileged
  • A command before the image name runs detached, while a command after the image name runs in the foreground

You start web with docker run -d --name driftwood-web driftwood/web and cannot reach it at localhost:8000. Why?

  • No port was published — without -p the container is reachable only from inside its Docker network
  • Detached mode (-d) silently disables host port mapping for any container running in the background
  • The --name flag bound the service to the name driftwood-web instead of to localhost
  • The host firewall blocks port 8000 by default and must be opened manually before a -p mapping works

You got correct