What is a Docker HEALTHCHECK, and how does it affect container status?

6 minintermediatehealthcheckcontainer-lifecycle

Quick Answer

A HEALTHCHECK instruction defines a command Docker periodically runs inside the container to determine whether the application is actually working correctly, not just whether its process happens to still be alive. Based on the command's exit code, Docker tracks the container's health as starting, healthy, or unhealthy — visible in docker ps and usable by other tooling (like Compose's depends_on: condition: service_healthy) to make decisions about a container's real readiness, not just whether its process is technically running.

Detailed Answer

Why "the process is running" isn't the same as "the application works"

A container can be in the Running state (its main process hasn't crashed) while the application inside is completely non-functional. It could be deadlocked, unable to reach a required database, or stuck in an infinite loop that never actually serves a request. Docker's basic process-based status has no way to distinguish "truly working" from "technically still running but broken" without an explicit healthcheck telling it how to actually verify functionality.

Defining a HEALTHCHECK

HEALTHCHECK --interval=30s --timeout=5s --retries=3 --start-period=10s \
  CMD curl -f http://localhost:3000/health || exit 1
  • --interval — how often to run the check (every 30 seconds here).
  • --timeout — how long to wait for the check command itself before considering that attempt a failure.
  • --retries — how many consecutive failures are needed before marking the container unhealthy (a single blip doesn't immediately flip the status).
  • --start-period — a grace period after the container starts during which failures don't count against the retry threshold. This is similar in spirit to Kubernetes's startup probe (see that stack). It is useful for applications with a legitimately slow startup that shouldn't be judged by the same strict timing as steady-state health checks.
  • CMD ... — the actual command run to check health; exit code 0 means healthy, any non-zero exit code means unhealthy for that attempt.

Observing health status

docker ps
# STATUS: Up 2 minutes (healthy)
# or:
# STATUS: Up 5 minutes (unhealthy)
docker inspect --format='{{.State.Health.Status}}' my-container
docker inspect --format='{{json .State.Health}}' my-container | jq

The container's health status is visible directly in docker ps output. docker inspect reveals the full history of recent check results, which is useful for diagnosing exactly when and why a container transitioned to unhealthy.

How this differs from (and relates to) Kubernetes's probes

Docker's HEALTHCHECK is a single, combined mechanism roughly analogous to Kubernetes's liveness probe concept (see that stack's question). However, plain Docker itself has no automatic action tied to an unhealthy status by default. It is purely informational unless something else actively reacts to it — for example, a restart policy combined with a health-aware supervisor, an orchestrator, or Compose's condition: service_healthy dependency. This is a meaningful difference from Kubernetes, where a failing liveness probe automatically and directly triggers a container restart. Plain Docker's HEALTHCHECK alone doesn't restart anything by itself. You generally need Docker Swarm, Kubernetes, or custom tooling layered on top to actually act on an unhealthy status.

A common practical use: gating startup order in Compose

services:
  db:
    image: postgres:16
    healthcheck:
      test: ["CMD-SHELL", "pg_isready -U postgres"]
      interval: 5s
      retries: 5
  app:
    image: myapp:1.0
    depends_on:
      db:
        condition: service_healthy

This ensures the app service doesn't start until db's healthcheck actually reports healthy. This is a stronger guarantee than Compose's plain depends_on, which by default only waits for the dependency container to start, not to actually be ready (see the Compose topic for more on this important distinction). Add a HEALTHCHECK to any image where "the process is running" genuinely isn't sufficient evidence that the application works. This matters especially for services with real startup dependencies (a database connection, a cache warm-up), where a naive restart policy or dependency ordering based purely on process-start would be unreliable.