Skip to main content
RunBook Academy

Docker & ContainersXXIII Β· High AvailabilityRecovery layers

What restart policies and healthchecks cannot recover

Intermediate⏱ ~20 min

What you'll learn

  • Map each failure mode to the recovery layer that actually catches it
  • Explain the documented conditions under which a restart policy takes effect
  • Identify the failure modes that no single-host mechanism recovers

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-11

Not yet marked complete on this device.

You have set restart: unless-stopped and written a HEALTHCHECK. The reasonable expectation is that the container now recovers from things.

It recovers from some things. This lesson is the map: which failure modes each layer catches, which it detects without acting on, and which nothing available on a single host will address. The last column is the one that decides whether you need a second host.

The taxonomy

Failure modeRestart policyHEALTHCHECKActually recovered by
Process crashes, non-zero exitcatchesnot involvedthe restart policy
Process exits zero (job finished, bad config path)only always / unless-stoppednot involvedthe right policy, or fixing the exit
Process alive but wedged β€” deadlock, exhausted worker poolmissesdetectsnothing on standalone Docker
Memory leak, OOM killedcatchesmay detect firstrestart, but it will recur
Container fine, dependency downmissesdetects, if it probes the dependencyfixing the dependency
Disk full on the hostmissesusually detectsa human, or capacity alerting
Host rebootalways / unless-stopped restore itnot involvedthe restart policy plus the daemon enabled at boot
Docker daemon restart or upgradestops everything without live-restorenot involvedlive-restore
Kernel panic, PSU failure, disk failuremissesmissesa second host
Bad image promoted to productionmissesmay detectrollback

Two rows deserve the rest of the lesson: the wedged process, and the daemon.

The wedged process

A process that is running but not working is invisible to the restart policy. The policy is a reaction to an exit; nothing exited.

This is the failure mode that matters most in production, because it is what most application bugs look like. A connection pool exhausted by a slow dependency, a deadlock between two locks, a garbage collector thrashing, a thread pool full of requests waiting on a socket that will never answer β€” in every case the process is alive, docker ps says Up 4 days, and the service is down.

The healthcheck detects it. And then, on standalone Docker, nothing happens.

Read-only / Safethe state that persists indefinitely
$ docker ps --format '{{.Names}} β€” {{.Status}}'
proj-api-1 β€” Up 4 days (unhealthy)
proj-web-1 β€” Up 4 days (healthy)

Illustrative output

Up 4 days (unhealthy) is a stable state. The daemon will report it for as long as the container runs. It does not restart the container, it does not stop routing to it, and it does not tell anyone.

The usual answer is a watcher: a small container subscribed to docker events that restarts anything entering unhealthy.

Read-only / Safewhat such a watcher sees
docker events \
--filter 'event=health_status' \
--format '{{.Time}} {{.Actor.Attributes.name}} {{.Status}}'

It works, and it has a real cost: the watcher needs the Docker socket, and the Docker socket is root on the host. You are trading a class of application outage for a large increase in blast radius if that container is ever compromised. If you take that trade, put the socket behind a proxy that permits only the calls the watcher needs, and read the daemon-socket lesson in the certificates part first.

What β€œthe restart policy takes effect” actually means

Two documented behaviours surprise people, and both explain incidents that otherwise look like Docker ignoring configuration.

The ten-second rule. The documentation states that a restart policy only takes effect after a container starts successfully, and that starting successfully β€œmeans that the container is up for at least 10 seconds and Docker has started monitoring it.” A container that dies in two seconds every time is not in the regime the policy was designed for.

Manual stops are sticky. When you manually stop a container, the restart policy β€œis ignored until the Docker daemon restarts or the container is manually restarted”. This is the intended behaviour and it is also the trap: a container you stopped during an incident at 02:00 comes back on its own when the host reboots for patching three weeks later, in a state nobody expects.

The retry behaviour differs by policy. on-failure accepts a maximum retry count β€” on-failure:5 gives up after five. always and unless-stopped take no such limit, and the daemon keeps retrying with a growing delay between attempts. A container in that state is not β€œstuck”; it is doing exactly what you asked, forever.

Read-only / Safeis it flapping
docker inspect proj-api-1 --format \
'restarts={{.RestartCount}} status={{.State.Status}} exit={{.State.ExitCode}} oom={{.State.OOMKilled}} started={{.State.StartedAt}}'

A RestartCount in the hundreds against a StartedAt from four minutes ago is a crash loop wearing the costume of a healthy container. docker ps will show it as Up 3 seconds if you happen to look during an attempt.

The daemon is a single point of failure

Restarting the Docker daemon β€” for an upgrade, or because it wedged β€” stops every container on the host, unless live restore is enabled:

Configuration change/etc/docker/daemon.json
{
"live-restore": true
}

With it, containers keep running across a daemon restart. This turns a Docker upgrade from a full-stack outage into a control-plane blip, and it is the single highest-value line in daemon.json for a single-host deployment.

Its limits are worth stating plainly: it covers daemon restarts, not host reboots; while the daemon is down you have no API, so nothing can be started, stopped or inspected; and container logs are not being collected by the daemon during that window.

Sanity check

Knowledge check Β· 4 questions

  1. Q1. A container has restart: unless-stopped and a working HEALTHCHECK. Its worker pool deadlocks: the process is alive but serves nothing. What happens?

  2. Q2. According to the documentation, when does a restart policy take effect?

  3. Q3. Which failure modes does live-restore protect against? Select all that apply.

  4. Q4. A container configured with restart: always, which you then stopped manually with docker stop, can start again on its own when the host next reboots.

Passing score: 75%. Answers are checked in this browser.