Skip to main content
RunBook Academy

Docker & ContainersXX · Health & Failure DetectionRestart policies

Restart policies — no, always, unless-stopped, on-failure

Intermediate⏱ ~22 mindocker

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

Not yet marked complete on this device.

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

PolicyProcess exits non-zeroProcess exits zeroDaemon restarts (container running)Daemon restarts (container manually stopped)
nostays exitedstays exitedstays exitedstays exited
on-failure[:N]restarts, up to N if givenstays exitedstays exitedstays exited
alwaysrestartsrestartsstartsstarts
unless-stoppedrestartsrestartsstartsstays 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

Read-only / Safewhat a loop looks like
$ 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.0

Illustrative output

Read-only / Safethe four facts that identify the cause
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 codeAlmost always means
0the process finished its work — with always/unless-stopped this restarts forever; the policy is wrong, not the container
1application error — read the logs
125the daemon rejected the docker run itself; a bad flag
126the command was found but could not be executed; usually a missing execute bit on an entrypoint script
127the command was not found in the image; usually a typo or a shell missing from a slim base
137killed by SIGKILL — with OOMKilled: true it is the memory limit; without it, something sent a KILL
139segmentation fault
143terminated 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.

Read-only / Safethe precondition
# 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 -k2

On 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:

  1. containerd and dockerd start.
  2. Every container with always starts, whatever state it was in.
  3. Every container with unless-stopped starts unless it was deliberately stopped.
  4. Containers with no or on-failure stay stopped.
  5. All of them start at roughly the same moment.

Choosing a policy

WorkloadPolicyReasoning
Long-running production serviceunless-stoppedcomes back from every failure and every reboot, and respects a deliberate stop
Service that must never be off, even after you stopped italwaysthe daemon overrides your stop on restart; choose this knowingly
Batch job or migration that should run oncenoa zero exit is success; restarting it is a bug
Job that should retry a bounded number of timeson-failure:3retries on error only, gives up after three, and does not come back after a daemon restart
One-off debugging containerno with --rmand note that --rm combined with --restart is rejected outright by the daemon
Sidecar whose lifetime should match its parentnoCompose 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.

Configuration changeaudit and fix
# 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:

Configuration changecompose.yaml
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: 30s

Note 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

Service impact possibleprove the policy does what you think
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

  1. Q1. What is the only behavioural difference between `always` and `unless-stopped`?

  2. Q2. A batch worker with `restart: on-failure` was running when you upgraded Docker and restarted the daemon. What is its state afterwards?

  3. Q3. Which statements about the restart backoff are accurate? Select all that apply.

  4. 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.