Docker Healthchecks: Self-Healing Containers
Make containers report their own readiness with HEALTHCHECK and compose restart policies, so failed services restart before anyone notices.
Netbay Cloud Team
Netbay Engineering
On this page
A container that is running is not the same as a service that is working. A web server can be alive, accepting TCP, and still return 500s on every request — and an orchestrator or restart policy that only looks at process liveness will pat it on the head and move on. Healthchecks close that gap: the container itself runs a probe on an interval, reports healthy or unhealthy, and the runtime acts on the signal. Combined with restart policies, a healthcheck turns a flaky service into one that rehabilitates itself. This guide covers HEALTHCHECK in Dockerfiles, healthchecks in Compose, and the honest limits of the pattern.
What a Healthcheck Actually Checks
A HEALTHCHECK is a command the container runs internally, on a schedule, with a contract: exit 0 means healthy, nonzero means unhealthy. The point of the command is to exercise whatever "working" means for your service — usually an HTTP or TCP endpoint that returns a felicitous status only when dependencies are connected and the app is answering. The Dockerfile embeds the probe so the contract travels with the image.
FROM nginx:alpine
EXPOSE 80
# interval=30s timeout=3s start_period=10s retries=3
# Start period lets slow boot avoid false negatives
HEALTHCHECK --interval=30s --timeout=3s --start-period=10s --retries=3 \
CMD wget -q -O /dev/null http://127.0.0.1/ || exit 1wget (or curl) hitting the local service via the container's own loopback is the prototypical probe; it confirms the listener, the worker, and the app's HTTP stack in one request. The start_period attribute is worth special attention — probes before the service is ready to answer would mark a healthy-but-booting container unhealthy, so the period gives legitimate startup its own grace window.
Compose Healthchecks and Dependencies
Compose lets you define the healthcheck in the stack file instead of the image, which is convenient when a single image serves multiple roles with different readiness semantics — a worker needs no HTTP probe, a web instance does. The same file can also express dependency ordering: a service can wait for a dependency to report healthy before starting, which fixes the classic race of an app booting before its database accepts connections.
name: shop
services:
db:
image: postgres:16-alpine
environment:
POSTGRES_PASSWORD: changeme-local
healthcheck:
test: ["CMD-SHELL", "pg_isready -U postgres"]
interval: 10s
timeout: 5s
retries: 5
start_period: 15s
web:
build: .
depends_on:
db:
condition: service_healthy
restart: unless-stoppedThe CMD-SHELL test form runs the command through a shell — needed for pipes and for pg_isready, which checks only that Postgres is accepting connections, not that your schema exists. depends_on with condition: service_healthy is the important part: web will not start its own boot sequence until Postgres has reported healthy at least once.
Acting on the Signal: Restart Policies
A healthcheck that reports and is never acted on is a dashboard ornament. restart: unless-stopped (or a runtime restart policy) makes the runtime react: when the container is unhealthy and the policy is configured, the daemon restarts it and the healthcheck cycle begins again. This is self-healing at the smallest scale — not orchestration, but a locally attached nurse that notices when the heart stops answering and kicks it.
Choosing start_period and retries Sanely
The two knobs that most tune themselves wrong are start_period and retries. If the container routinely needs 20 seconds to become useful, set start_period above that; otherwise every deploy is a couple of ugly restart loops. If a single transient probe failure triggers a restart, a momentary load spike becomes a crash cycle; a small retries value tolerates that while still catching permanent failure promptly. Tune one at a time, then pressure-test with a deliberately broken dependency to confirm the restart actually fires.
Observing Health From the Outside
Health status is visible in docker ps (the STATUS column shows healthy, unhealthy, or starting), and scriptable through docker inspect for dashboards and alerting.
docker ps
docker inspect --format '{{ .State.Health.Status }}' web
docker inspect --format '{{ range .State.Health.Log }}{{ .ExitCode }} {{ .Output }}{{ end }}' webThe Log range prints the recent probe results with their output, which is the fastest way to see why a container flips unhealthy — the probe's own stderr is right there. Wiring that status into a cron check or a monitoring probe is a small step from "container restarts" to "a page fires when a service dies permanently".
Takeaway
Healthchecks move liveness from "is the process alive" to "is the service answering the way customers see it", and restart policies close the loop so the container rehabilitates itself. Probe the real endpoint, give boot its grace period, gate dependencies on healthy, and inspect the probe log when things misbehave. On a Netbay VPS you own the whole stack, so you can wire healthcheck signals into systemd or a cron alert in minutes — start your first self-healing service at netbayhosts.in.
Keep reading
Follow along on a real VPS
Deploy Linux in under 60 seconds
These guides are written against Ubuntu, Debian, and RHEL-family images — the same ones on NetBay.
Deploy an instance