Docker HEALTHCHECK: Dockerfile and Compose Health Checks
Updated July 27, 2026
HEALTHCHECK --interval=30s --timeout=3s --retries=3 CMD curl -f http://localhost:8080/health || exit 1 — or per service in Compose. The status shows in docker ps and drives depends_on: condition: service_healthy for startup ordering. Note that plain Docker never restarts an unhealthy container by itself — the status is information for you and for orchestrators.docker inspect --format='{{json .State.Health}}' <container-name>Step 1: add a HEALTHCHECK to the Dockerfile
The options: --interval (default 30s) between checks, --timeout (30s) per attempt, --retries (3) consecutive failures before unhealthy, --start-period (0s) during which failures do not count, and --start-interval (5s, Docker Engine 25+) for faster probing during startup. The check command runs inside the container, so the binary it uses (curl, wget) must exist in the image.
Exit code 0 is healthy and 1 is unhealthy; the || exit 1 idiom normalizes any curl failure code to 1.
HEALTHCHECK --interval=30s --timeout=3s --start-period=15s --retries=3 \
CMD curl -f http://localhost:8080/health || exit 1
# minimal images without curl:
HEALTHCHECK CMD wget -q --spider http://localhost:8080/health || exit 1Step 2: define health checks in Compose
The Compose healthcheck block overrides or supplies the same settings per service. Use the exec-array form ["CMD", ...] to skip the shell, or "CMD-SHELL" when you need pipes and ||. A service can also opt out of an image-defined check with disable: true.
services:
api:
image: myapp:1.4
healthcheck:
test: ["CMD-SHELL", "curl -f http://localhost:8080/health || exit 1"]
interval: 30s
timeout: 3s
retries: 3
start_period: 15sStep 3: gate startup order with depends_on
Plain depends_on only orders container creation: the app still races its database. The long form with condition: service_healthy makes Compose wait until the dependency's healthcheck passes before starting the dependent service, which eliminates most "connection refused on first boot" errors.
services:
api:
depends_on:
db:
condition: service_healthy
db:
image: postgres:16
healthcheck:
test: ["CMD-SHELL", "pg_isready -U postgres"]
interval: 5s
timeout: 3s
retries: 10Step 4: inspect health status and failing output
docker ps shows the summary (healthy, unhealthy, health: starting) in the STATUS column. The full detail (including stdout/stderr of the last five check runs, which is where the actual failure reason lives) is in docker inspect under .State.Health.
docker ps --format 'table {{.Names}}\t{{.Status}}'
docker inspect --format='{{json .State.Health}}' <container-name>
# .Log[] contains the output of recent check runsWhat unhealthy does (and does not) trigger
The Docker engine itself takes no action on unhealthy: no restart, no replacement: restart policies react to the process exiting, not to health status. Docker Swarm does replace unhealthy tasks, and Compose uses health for dependency gating. Kubernetes ignores Dockerfile HEALTHCHECK entirely and uses its own liveness/readiness probes. If you need auto-restart on unhealthy with plain Docker, a sidecar like willfarrell/autoheal (watching the Docker socket) is the common pattern.
A healthcheck answers one narrow question: is this process responding? AI agents need the same primitive at a higher level, not "is the agent running" but "is it doing what policy allows". Nexus runtime hooks act as the healthcheck layer for agent behavior: every action is captured as it happens, policy violations are blocked in-flight rather than discovered later, and the equivalent of the .State.Health log is a full audit trail of what each agent did and why.
Explore Swfte NexusCommon questions
- Does Docker restart a container when its healthcheck fails?
- No. Standalone Docker only marks the container unhealthy; restart policies trigger on process exit, not health status. Automatic replacement of unhealthy containers is an orchestrator feature: Swarm does it natively, and on plain Docker people run an autoheal sidecar.
- What is the difference between start-period and interval?
- start-period is a grace window after container start during which failed checks do not count toward the unhealthy threshold: for apps that boot slowly. interval is the steady-state spacing between checks. Docker 25+ adds start-interval to probe faster during the start period so containers report healthy sooner.
- Why does my healthcheck fail with "curl: not found"?
- The check runs inside the container, and slim/alpine/distroless images often ship without curl. Use wget --spider if BusyBox wget is present, install curl in the image, or ship a tiny healthcheck binary or script that uses the app runtime itself (e.g. node -e with fetch).
- Does Kubernetes use the Dockerfile HEALTHCHECK?
- No: Kubernetes ignores it and relies on its own liveness, readiness, and startup probes defined in the pod spec. If you are migrating from Compose, translate the healthcheck into a readinessProbe (traffic gating) and, where genuinely needed, a livenessProbe (restart).
- How do I see why a container is unhealthy?
- Run docker inspect --format='{{json .State.Health}}' on the container. The Log array holds the last few check executions with their exit codes and captured output: the actual error message from your check command is there.