Container Lifecycle, Restart Policies and Resource Limits

Intermediate
12 min

Container Lifecycle, Restart Policies and Resource Limits

A container that stops cleanly, comes back after a crash and cannot starve its neighbours takes more than docker run. In this lesson you will learn the states a container moves through, how Docker signals a process to stop, what exit codes mean, how restart policies keep services alive, and how to cap CPU, memory and process counts.

The Lifecycle States

Every container is in one of a few states, visible in the STATUS column of docker ps -a or in .State.Status from docker inspect:

| State | Meaning | Typical transition | |-------|---------|--------------------| | created | Filesystem and config exist, process not started | docker create | | running | Main process is alive | docker start or docker run | | paused | Processes frozen with cgroup freezer | docker pause / docker unpause | | exited | Main process ended; filesystem kept | docker stop, crash, or normal completion | | restarting | Docker is applying a restart policy | automatic |

docker run is simply docker create followed by docker start. An exited container keeps its writable layer, so you can still read logs and copy files out until you run docker rm. Use --rm for throwaway containers so they are deleted on exit.

Stopping Gracefully: SIGTERM, Timeout, SIGKILL

docker stop sends SIGTERM to PID 1 inside the container, waits for a grace period (10 seconds by default), then sends SIGKILL. A well-behaved application catches SIGTERM, finishes in-flight requests and exits:

javascript
// Node.js example: exit cleanly when Docker asks process.on("SIGTERM", () => { server.close(() => process.exit(0)); });
bash
docker stop -t 30 api # give the app 30 seconds instead of 10 docker kill api # SIGKILL immediately, no grace period docker kill -s SIGHUP web # send a different signal

If your application never handles SIGTERM, every stop takes the full timeout and ends in a forced kill. The usual cause is a shell wrapper that swallows signals, a problem the CMD vs ENTRYPOINT lesson solves.

Reading Exit Codes

The exit code of the main process is recorded on the container. Check it with docker ps -a (shown as Exited (137)) or docker inspect --format '{{.State.ExitCode}}':

  • 0: normal completion; 1 to 125: application error, so read docker logs.
  • 126 / 127: command not executable / command not found inside the image.
  • 137: killed by SIGKILL (128 + 9): docker kill, an expired stop timeout, or the kernel's OOM killer. .State.OOMKilled tells them apart.
  • 143: terminated by SIGTERM (128 + 15), the normal outcome of docker stop for apps that do not exit with 0.

Restart Policies

By default a container that exits stays exited. Restart policies tell the daemon to bring it back:

| Policy | Behaviour | |--------|-----------| | no | Never restart (default) | | on-failure[:N] | Restart only on a non-zero exit code, at most N times | | always | Always restart, including after a daemon reboot, even if you stopped it manually before the reboot | | unless-stopped | Like always, but stays stopped if you explicitly ran docker stop |

bash
docker run -d --restart unless-stopped --name api my-api:1.0 docker update --restart on-failure:5 api # change it on a running container docker inspect --format '{{.HostConfig.RestartPolicy.Name}}' api

Restarts use an exponential back-off so a crash-looping container does not spin the CPU. unless-stopped is the usual choice for long-running services on a single host; in Compose the same setting is the restart: key.

Resource Limits

Without limits a single container can consume all host memory or CPU. The flags map directly to cgroup controls:

bash
docker run -d --name api \ --memory 512m \ # hard cap; exceeding it triggers the OOM killer --memory-swap 512m \ # equal to --memory means no swap at all --memory-reservation 256m \ # soft limit used under host memory pressure --cpus 1.5 \ # at most 1.5 CPU cores worth of time --cpu-shares 512 \ # relative weight versus other containers (default 1024) --pids-limit 200 \ # prevents fork bombs my-api:1.0

Watch the effect with docker stats: the MEM USAGE / LIMIT column shows the cap you set. When a process exceeds --memory, the kernel kills it and the container exits with code 137 and OOMKilled: true. Size the limit above the application's real peak, and remember that runtimes like the JVM need their own heap flags to respect it, which the language-specific lessons cover. Two frequent mistakes: setting --memory without --memory-swap (which silently allows an equal amount of swap on top), and using --restart always on a one-off task, which restarts forever after finishing successfully.

Quick Quiz
Question 1 of 3

What does `docker stop` do if the process ignores SIGTERM?

Key Takeaways

  • Containers move through created, running, paused, exited; docker run is create plus start.
  • docker stop sends SIGTERM, waits for the grace period, then SIGKILL; applications should handle SIGTERM.
  • Exit codes above 128 encode a signal: 137 is SIGKILL (often OOM), 143 is SIGTERM.
  • --restart unless-stopped is the standard choice for long-running services; on-failure:N suits tasks.
  • --memory, --memory-swap, --cpus and --pids-limit enforce cgroup limits; docker update changes them live.

Next lesson: Writing a Dockerfile — describe your own image as code.

Container Lifecycle, Restart Policies and Resource Limits - Docker | CodeYourCraft | CodeYourCraft