sehaj/learn
Docker/03 Running containers

03.01 · with code · 10 min

The container lifecycle

Created, running, paused, exited: what each state means, which command moves a container between them, and what the exit code is telling you.

The core idea

A container is always in one of a few states, and every lifecycle command moves it from one to another. Knowing the states, and what a stopped container's exit code means, is most of what you need to debug "why isn't my container running?"

The lifecycle
  1. 01

    Created

    docker create: filesystem and config exist, no process yet.

  2. 02

    Running

    docker start: the main process is up. docker pause freezes it.

  3. 03

    Exited

    The main process ended, or docker stop / kill ended it. Files remain.

  4. 04

    Removed

    docker rm: the container and its writable layer are gone.

docker run is just create + start. A running container can also be Paused, and a crashing one can be Restarting.

Walk through it

❯terminal
$ docker create --name life nginx:alpine
$ docker ps -a --filter name=life --format '{{.Names}} {{.Status}}'
life Created
$ docker start life
life Up Less than a second
$ docker pause life
life Up Less than a second (Paused)
$ docker unpause life
$ docker stop life
life Exited (0) Less than a second ago
$ docker rm life

(Each status line is the same docker ps -a command, run after the step above it.)

docker pause freezes every process in the container using the cgroup freezer: they stop getting CPU time but keep their memory. It's rarely what you want in production, but useful to know it exists.

Stopping: polite, then forceful

There are two ways to end a container:

  • docker stop sends SIGTERM (or the image's STOPSIGNAL), waits for the stop timeout, then sends SIGKILL.
  • docker kill sends SIGKILL immediately. No cleanup, no flushing.

The stop timeout is 10 seconds by default on Docker Engine. Check what a container uses with docker inspect -f '{{.Config.StopTimeout}}' NAME, and override it per command with docker stop -t 30 or per container with docker run --stop-timeout 30.

What happens next depends on whether PID 1 handles the signal. sleep doesn't:

❯terminal
$ docker run -d --name sleeper alpine sleep 1000
$ docker stop -t 10 sleeper # waits the full 10s
$ docker inspect -f '{{.State.ExitCode}}' sleeper
137
$ docker run -d --init --name sleeper2 alpine sleep 1000
$ docker stop sleeper2 # returns immediately
$ docker inspect -f '{{.State.ExitCode}}' sleeper2
143

Without --init, sleep is PID 1 and ignores SIGTERM, so Docker waits out the timeout and kills it. With --init, a tiny init process is PID 1; it forwards SIGTERM to sleep, which dies from it straight away.

Reading exit codes

CodeUsually means
0the process finished normally
1 (or any small number)the app exited with an error; check docker logs
125docker run itself failed (bad flag, name taken)
126 / 127the command couldn't run / wasn't found in the image
137killed with SIGKILL (128 + 9): docker kill, a stop timeout, or the OOM killer
143ended by SIGTERM (128 + 15) without handling it more gracefully

For 137, docker inspect -f '{{.State.OOMKilled}}' tells you whether memory was the reason (03.06 CPU and memory limits).

Predict first

A container shows Exited (137). OOMKilled is false. What's the most likely explanation?

Commit to an answer before you read on.
CheckpointMake a container exit every way

Produce each of these and confirm the code with docker inspect -f '{{.State.ExitCode}}': 0 (alpine true), 1 (alpine false), 127 (alpine nosuchcommand), 137 (docker kill on nginx). Then stop the notes API and check it exits with 0, because it handles SIGTERM itself.

STUCK? ▸

If docker stop on the notes API gives 137 instead of 0, the app didn't get SIGTERM: check the CMD is the exec form.

Recap

Interview questions

JuniorfreqMust-knowConcept

What's the difference between docker stop and docker kill?

MidfreqMust-knowDebugging

A container keeps exiting with code 137. How do you find out why?

Report a mistakeEdit this page