Docker & ContainersXX · Health & Failure DetectionRestart policies
Restart policies — no, always, unless-stopped, on-failure
What you'll learn
- Predict what each policy does on exit, on daemon restart and after a manual stop
- Describe the restart backoff and the ten-second rule that resets it
- Name the single behavioural difference between `always` and `unless-stopped`
- Diagnose a restart loop from `RestartCount`, exit codes and events
- Explain why a restart policy does nothing for a container that is unhealthy but running
Prerequisites
Verified against Docker Engine 29.x · Docker Engine 28.x · Docker Compose 2.x · containerd 2.x · runc 1.2.x · BuildKit 0.20+ · Linux kernel 5.15+ · Ubuntu 24.04 LTS · Debian 12 (Bookworm) · 2026-08-12
A restart policy tells the daemon what to do when a container’s main process exits. That is its entire scope, and holding on to it resolves most of the confusion in this area — including the one the previous lesson ended on.
An unhealthy container has not exited. It is running, its PID 1 is
alive, and it is still bound to its published port. The restart policy
is therefore not consulted, will never be consulted, and adding
restart: always changes nothing about it. Restart policies and
health status are two independent mechanisms that never speak to each
other.
What restart policies do cover is worth knowing exactly, because three of their behaviours are surprising.
The four policies
| Policy | Process exits non-zero | Process exits zero | Daemon restarts (container running) | Daemon restarts (container manually stopped) |
|---|---|---|---|---|
no | stays exited | stays exited | stays exited | stays exited |
on-failure[:N] | restarts, up to N if given | stays exited | stays exited | stays exited |
always | restarts | restarts | starts | starts |
unless-stopped | restarts | restarts | starts | stays stopped |
Two cells in that table are the whole story.
on-failure does not survive a daemon restart. The documentation
is explicit: the policy “only prompts a restart if the container exits
with a failure. It doesn’t restart the container if the daemon
restarts.” A batch worker with on-failure that was running happily
when you upgraded Docker is simply not running afterwards, and nothing
logs an error about it. This is the most commonly missed row in the
table.
always and unless-stopped differ in exactly one cell. Both
restart on any exit. Both come back on daemon restart if they were
running. The only difference is what happens to a container you
stopped on purpose when the daemon later restarts: always brings
it back, unless-stopped leaves it alone.
The ten-second rule
“A restart policy only takes effect after a container starts successfully. In this case, starting successfully means that the container is up for at least 10 seconds and Docker has started monitoring it.”
A container that fails in under ten seconds on its very first run is not considered to have started, so the policy does not engage. This prevents a container with a typo in its command from spinning uselessly forever.
It also produces a genuinely confusing symptom the first time you meet
it: a container with restart: always that exits immediately and
then does not restart. Nothing is broken. The daemon has decided it
never started in the first place. The evidence is in the container’s
state, not in the policy.
The backoff
Once the policy does engage, the daemon does not restart in a tight loop. It backs off, and the exact behaviour is worth knowing because it is what a restart loop looks like from the outside.
Diagnosing a restart loop
$ docker ps -a --filter status=restarting --format 'table {{.Names}}\t{{.Status}}\t{{.Image}}'NAMES STATUS IMAGE
worker Restarting (1) 43 seconds ago example/worker:2.4.1
importer Restarting (137) 8 seconds ago example/importer:1.9.0Illustrative output
CONTAINER=worker
# How many times, and when did the current attempt start?
docker inspect -f 'restarts={{.RestartCount}} started={{.State.StartedAt}} exit={{.State.ExitCode}} oom={{.State.OOMKilled}}' "$CONTAINER"
# The last words before each death.
docker logs --tail 40 --timestamps "$CONTAINER"
# Watch the loop live: die and start events, with the exit code.
docker events --filter "container=$CONTAINER" --filter 'event=die' --filter 'event=start' --format '{{.Time}} {{.Status}} exitCode={{index .Actor.Attributes "exitCode"}}'The exit code narrows it faster than the logs do:
| Exit code | Almost always means |
|---|---|
0 | the process finished its work — with always/unless-stopped this restarts forever; the policy is wrong, not the container |
1 | application error — read the logs |
125 | the daemon rejected the docker run itself; a bad flag |
126 | the command was found but could not be executed; usually a missing execute bit on an entrypoint script |
127 | the command was not found in the image; usually a typo or a shell missing from a slim base |
137 | killed by SIGKILL — with OOMKilled: true it is the memory limit; without it, something sent a KILL |
139 | segmentation fault |
143 | terminated by SIGTERM — a clean shutdown that the policy is now undoing |
137 with "OOMKilled": true is the one worth recognising instantly:
that is the cgroup memory limit, and no restart policy will fix it.
The container will be killed again at the same point every time,
usually with the interval between kills getting shorter as caches warm
faster.
Host reboot
This is the behaviour most people actually care about, and it has a precondition that has nothing to do with the policy.
# Is the daemon set to start at boot at all?
systemctl is-enabled docker.service
systemctl is-enabled containerd.service
# What would come back? Everything with always or unless-stopped.
docker ps -a --format '{{.Names}}\t{{.Status}}' --filter 'label=' 2>/dev/null >/dev/null
docker inspect -f '{{.Name}} {{.HostConfig.RestartPolicy.Name}} {{.State.Status}}' $(docker ps -aq) | sort -k2On a host installed from the Docker apt or yum repositories,
docker.service is enabled by default. On a host where someone
installed Docker to try something and never intended it to be
permanent, it may not be — and then every restart policy on the box is
decoration. systemctl is-enabled docker.service is a one-line check
that belongs in every post-build verification.
Given an enabled daemon, a reboot produces:
containerdanddockerdstart.- Every container with
alwaysstarts, whatever state it was in. - Every container with
unless-stoppedstarts unless it was deliberately stopped. - Containers with
nooron-failurestay stopped. - All of them start at roughly the same moment.
Choosing a policy
| Workload | Policy | Reasoning |
|---|---|---|
| Long-running production service | unless-stopped | comes back from every failure and every reboot, and respects a deliberate stop |
| Service that must never be off, even after you stopped it | always | the daemon overrides your stop on restart; choose this knowingly |
| Batch job or migration that should run once | no | a zero exit is success; restarting it is a bug |
| Job that should retry a bounded number of times | on-failure:3 | retries on error only, gives up after three, and does not come back after a daemon restart |
| One-off debugging container | no with --rm | and note that --rm combined with --restart is rejected outright by the daemon |
| Sidecar whose lifetime should match its parent | no | Compose does not model this; the parent’s health check does |
The default is no, which is almost never what a production container
wants. The single most common production defect in this area is not
choosing the wrong policy — it is not setting one at all, and finding
out at the next reboot.
# Everything with no restart policy at all.
docker inspect -f '{{.Name}} {{.HostConfig.RestartPolicy.Name}}' $(docker ps -q) | awk '$2=="no" || $2==""'
# Change one container without recreating it. The change is immediate
# and persists in the container's configuration.
CONTAINER=web
docker update --restart unless-stopped "$CONTAINER"
# Confirm it took.
docker inspect -f '{{.HostConfig.RestartPolicy.Name}}' "$CONTAINER"docker update --restart changes a running container’s policy without
recreating it, which is the rare Docker operation that is both useful
and free. Applying it across every container at once with
docker update --restart unless-stopped $(docker ps -q) is a
documented one-liner, and it is also a change to the boot behaviour of
the entire host — read the list from the first command before you run
the second.
In Compose, the equivalent is the restart key, and it is one of the
few Compose keys that requires recreating the container to apply
because it is part of the container’s host configuration:
services:
web:
image: example/web:1.4.2
restart: unless-stopped
migrate:
image: example/web:1.4.2
command: ["./manage.py", "migrate"]
restart: "no" # runs once; a zero exit is success
depends_on:
db:
condition: service_healthy
db:
image: postgres:17
restart: unless-stopped
healthcheck:
test: ["CMD-SHELL", "pg_isready -U postgres"]
interval: 10s
timeout: 3s
retries: 3
start_period: 30sNote restart: "no" in quotes. Unquoted, YAML parses no as the
boolean false, and Compose rejects it — one of the few places the
YAML 1.1 boolean rules still bite.
Verification that can fail
set -euo pipefail
CONTAINER=web
before=$(docker inspect -f '{{.RestartCount}}' "$CONTAINER")
echo "restarts before: $before"
# Kill PID 1 in the container. The policy should bring it back.
docker kill --signal=SIGKILL "$CONTAINER"
sleep 5
docker inspect -f 'status={{.State.Status}} restarts={{.RestartCount}} exit={{.State.ExitCode}}' "$CONTAINER"
after=$(docker inspect -f '{{.RestartCount}}' "$CONTAINER")
[ "$after" -gt "$before" ] && echo 'POLICY WORKS' || echo 'POLICY DID NOT FIRE'If RestartCount did not increase, the possibilities are short and
each is a real finding: the policy is no, the container had not been
up ten seconds when you killed it, or it had been manually stopped
earlier in its life and the policy is still suspended.
For the reboot case there is no substitute for rebooting. Do it once, on a new host, before it carries anything — the answer to “does this host come back on its own?” is worth more than any inspection of configuration, and it is a question that gets answered by accident at the worst possible time otherwise.
Knowledge check
Knowledge check · 4 questions
Q1. What is the only behavioural difference between `always` and `unless-stopped`?
Q2. A batch worker with `restart: on-failure` was running when you upgraded Docker and restarted the daemon. What is its state afterwards?
Q3. Which statements about the restart backoff are accurate? Select all that apply.
Q4. Setting `deploy.restart_policy.delay: 5s` staggers container startup after a host reboot so the host is not overwhelmed.
Passing score: 75%. Answers are checked in this browser.
Where next
Most restart loops are not really about the container that is looping; they are about something it depends on. The next lesson is about how one failure becomes several, and the patterns that stop it.