Docker & ContainersXXIII Β· High AvailabilityRecovery layers
What restart policies and healthchecks cannot recover
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
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 mode | Restart policy | HEALTHCHECK | Actually recovered by |
|---|---|---|---|
| Process crashes, non-zero exit | catches | not involved | the restart policy |
| Process exits zero (job finished, bad config path) | only always / unless-stopped | not involved | the right policy, or fixing the exit |
| Process alive but wedged β deadlock, exhausted worker pool | misses | detects | nothing on standalone Docker |
| Memory leak, OOM killed | catches | may detect first | restart, but it will recur |
| Container fine, dependency down | misses | detects, if it probes the dependency | fixing the dependency |
| Disk full on the host | misses | usually detects | a human, or capacity alerting |
| Host reboot | always / unless-stopped restore it | not involved | the restart policy plus the daemon enabled at boot |
| Docker daemon restart or upgrade | stops everything without live-restore | not involved | live-restore |
| Kernel panic, PSU failure, disk failure | misses | misses | a second host |
| Bad image promoted to production | misses | may detect | rollback |
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.
$ 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.
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.
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:
{
"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
Q1. A container has restart: unless-stopped and a working HEALTHCHECK. Its worker pool deadlocks: the process is alive but serves nothing. What happens?
Q2. According to the documentation, when does a restart policy take effect?
Q3. Which failure modes does live-restore protect against? Select all that apply.
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.