Running Containers

This content was generated with the assistance of AI and should be verified against the official documentation before being relied on in production; see the section bibliography for the reference material consulted while preparing these pages.

Running containers is where both 2015 books spend most of their pages (Docker: Up & Running chapters 5 and 6, Learning Docker chapter 2), and the narrative still holds: a container is a process started from an image, with its own lifecycle, configuration and state. This page rewrites that material against Docker Engine 29.8.x: the lifecycle, the docker run options you use daily, the commands that operate on running containers, and how signals and exit codes decide whether an application shuts down cleanly.

The container lifecycle

stateDiagram-v2 [*] --> Created: docker create Created --> Running: docker start [*] --> Running: docker run (create + start) Running --> Paused: docker pause Paused --> Running: docker unpause Running --> Exited: process exits / docker stop / docker kill Exited --> Running: docker start / restart policy Running --> Running: docker restart Exited --> Removed: docker rm Created --> Removed: docker rm Running --> Removed: docker rm -f Removed --> [*]
  • docker create prepares the container (writable layer, configuration, network endpoint) without starting it; docker start runs its process; docker run does both and, unless -d is given, attaches to its output.

  • docker stop sends the stop signal (SIGTERM unless the image sets STOPSIGNAL), waits for the stop timeout (10 seconds by default), then sends SIGKILL. docker kill sends SIGKILL (or --signal) immediately.

  • docker pause freezes every process in the container with the cgroup freezer; nothing receives a signal.

  • An exited container keeps its writable layer, logs and configuration until docker rm. --rm removes it automatically when it exits.

docker run and its most-used options

docker run options grouped into process and identity and filesystem and network and resources and metadata
Option Meaning

-d, --detach

Run in the background and print the container ID.

-it

Allocate a pseudo-TTY and keep STDIN open — for interactive shells.

--rm

Remove the container when it exits.

--name orders

A fixed name (otherwise Docker generates one). Names are unique per host and resolvable by DNS on user-defined networks.

-e KEY=value, --env-file .env

Environment variables; -e KEY alone copies the value from the calling shell.

-p 8080:8080, -p 127.0.0.1:5005:5005, -P

Publish container ports on the host (see Networking); -P publishes every `EXPOSE`d port on a random host port.

-v data:/var/lib/postgresql/data, --mount type=volume,src=data,dst=/data

Volumes, bind mounts and tmpfs (see Storage).

--network backend

Attach to a network instead of the default bridge.

-w /app, -u 10001:10001

Override the working directory and the user.

--entrypoint sh

Replace the image’s ENTRYPOINT; arguments after the image name replace CMD.

--init

Run a tiny init process (docker-init, based on tini) as PID 1 that forwards signals and reaps zombies.

--restart unless-stopped

Restart policy (see below).

--stop-timeout 30, --stop-signal SIGINT

How long docker stop waits before SIGKILL, and which signal it sends first.

--label team=orders

Metadata for filtering (docker ps --filter label=team=orders) and housekeeping.

--platform linux/amd64

Run a specific platform variant (emulated if it does not match the host).

--memory 512m --cpus 1.5

Resource limits (see Resources, Logging and Monitoring).

docker network create backend
docker run -d --name orders-db --network backend \
  -e POSTGRES_PASSWORD=dev-only-password -e POSTGRES_DB=orders \
  -v orders-db-data:/var/lib/postgresql \
  --restart unless-stopped postgres:18.1
docker run --rm -it --network backend postgres:18.1 psql -h orders-db -U postgres -d orders

Derived from docker container run and the postgres image documentation. The password is a development-only placeholder; real credentials never go on a command line, where shell history and docker inspect both keep them.

Overriding image defaults

Everything the image configures is a default the run command can override: CMD (arguments after the image name), ENTRYPOINT (--entrypoint), environment (-e), user (-u), working directory (-w), exposed ports (-p/--expose), health check (--health-cmd, --no-healthcheck) and stop signal (--stop-signal).

docker run --rm eclipse-temurin:25-jre java -version            # replaces CMD
docker run --rm --entrypoint /bin/sh eclipse-temurin:25-jre -c 'id && pwd'   # replaces ENTRYPOINT
docker run --rm -e JAVA_TOOL_OPTIONS=-XX:MaxRAMPercentage=75 orders-service:1.4.0

Working with running containers

docker ps                                   # running containers; -a includes exited ones
docker ps --format 'table {{.Names}}\t{{.Status}}\t{{.Ports}}'
docker exec -it orders-db psql -U postgres  # run another process inside the container
docker logs -f --since 10m orders-db        # follow the container's stdout/stderr
docker inspect orders-db                    # full JSON: config, state, mounts, networks
docker inspect --format '{{.State.Status}} {{.State.ExitCode}}' orders-db
docker top orders-db                        # processes in the container, as seen from the host
docker stats --no-stream                    # CPU, memory, network, block I/O per container
docker cp orders-db:/var/lib/postgresql/18/docker/postgresql.conf ./   # copy files out (or in)
docker attach orders-db                     # attach to PID 1's stdio (detach with Ctrl-P Ctrl-Q when -it)
docker port orders-db                       # published port mappings
docker update --memory 1g --memory-swap 1g orders-db   # change limits of a running container
docker wait orders-db                       # block until it exits, print the exit code

Derived from docker container and its ls, exec, logs, inspect, top, stats, cp, attach, port, update and wait sub-pages. docker exec is the modern answer to the nsenter workflow both books describe for getting inside a container; for images without a shell, see docker debug on Debugging Containers.

Signals, exit codes and graceful shutdown

The container’s main process is PID 1 inside its PID namespace, and PID 1 is special on Linux: the kernel does not apply default signal handlers to it, so a SIGTERM that the process does not explicitly handle is ignored. Combined with Docker’s stop sequence — SIGTERM, wait --stop-timeout, then SIGKILL — this produces the classic symptom of a container that always takes exactly 10 seconds to stop and then dies with exit code 137.

What to do:

  • Start the application directly, in exec form. ENTRYPOINT ["java", "-jar", "/app/app.jar"] makes the JVM PID 1; the JVM handles SIGTERM and runs its shutdown hooks. A shell-form entrypoint or a wrapper script makes /bin/sh PID 1 instead, and the signal never reaches Java. A wrapper script must end with exec java …​.

  • Use --init (or tini in the image) when the process does not handle signals or spawns children it does not reap.

  • Give the application time. Spring Boot’s graceful shutdown (server.shutdown=graceful, the default since Spring Boot 3.4) finishes in-flight requests within spring.lifecycle.timeout-per-shutdown-phase (30 seconds by default); make --stop-timeout (or Compose’s stop_grace_period) longer than that.

Exit codes tell you how a container ended:

Exit code Meaning

0

The process exited normally.

1, other small numbers

The application reported an error.

125

docker run itself failed (bad flag, daemon error).

126

The command could not be invoked (permission problem, not executable).

127

The command was not found.

128 + n

Killed by signal n: 137 = SIGKILL (after a stop timeout, or the OOM killer), 143 = SIGTERM handled by exiting.

time docker stop orders          # ~10 s means SIGTERM was ignored and SIGKILL followed
docker inspect --format '{{.State.ExitCode}} OOMKilled={{.State.OOMKilled}}' orders

Restart policies and process managers

Docker can restart a container when its process exits:

Policy Behaviour

no (default)

Never restart.

on-failure[:max-retries]

Restart only on a non-zero exit code, optionally a limited number of times.

always

Always restart, including when the daemon starts; a manual docker stop holds it until the daemon restarts.

unless-stopped

Like always, but a manually stopped container stays stopped across daemon restarts.

Restarts back off exponentially (starting at 100 ms) and a container must run for at least 10 seconds for the policy to count it as started. Use restart policies for single-host services instead of systemd units or supervisor scripts per container. Derived from Start containers automatically.

One process per container

The recommendation both books already made stands: one concern per container, one main process, with the container’s lifecycle equal to that process’s lifecycle. Logs, health, restarts, scaling and resource limits are then all per concern. A supervisor inside a container (supervisord, s6) is acceptable for tightly coupled helpers, but it hides failures from Docker and must be configured to exit when a critical child dies. See Run multiple processes in a container.

Cleaning up

docker container prune                         # remove all stopped containers
docker container prune --filter until=24h
docker rm -f $(docker ps -aq --filter label=purpose=scratch)
docker system prune                            # stopped containers, unused networks, dangling images, build cache
docker system df                               # what is using disk space