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?"
- 01
Created
docker create: filesystem and config exist, no process yet.
- 02
Running
docker start: the main process is up. docker pause freezes it.
- 03
Exited
The main process ended, or docker stop / kill ended it. Files remain.
- 04
Removed
docker rm: the container and its writable layer are gone.
Walk through it
$ docker create --name life nginx:alpine$ docker ps -a --filter name=life --format '{{.Names}} {{.Status}}'life Created$ docker start lifelife Up Less than a second$ docker pause lifelife Up Less than a second (Paused)$ docker unpause life$ docker stop lifelife 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 stopsendsSIGTERM(or the image'sSTOPSIGNAL), waits for the stop timeout, then sendsSIGKILL.docker killsendsSIGKILLimmediately. 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:
$ docker run -d --name sleeper alpine sleep 1000$ docker stop -t 10 sleeper # waits the full 10s$ docker inspect -f '{{.State.ExitCode}}' sleeper137$ docker run -d --init --name sleeper2 alpine sleep 1000$ docker stop sleeper2 # returns immediately$ docker inspect -f '{{.State.ExitCode}}' sleeper2143
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
| Code | Usually means |
|---|---|
0 | the process finished normally |
1 (or any small number) | the app exited with an error; check docker logs |
125 | docker run itself failed (bad flag, name taken) |
126 / 127 | the command couldn't run / wasn't found in the image |
137 | killed with SIGKILL (128 + 9): docker kill, a stop timeout, or the OOM killer |
143 | ended 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).
A container shows Exited (137). OOMKilled is false. What's the most likely explanation?
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
What's the difference between docker stop and docker kill?
A container keeps exiting with code 137. How do you find out why?