Compose Health Check
A configuration in a Docker Compose service that defines a command Docker runs periodically to determine if a container is healthy. Other services use depends_on with condition: service_healthy to wait until a dependency passes its health check before starting.
Compose Health Check — Waiting for Dependencies to Be Ready
What Is a Compose Health Check in Simple Terms?
Docker starts containers quickly — but services inside containers take time to be ready. PostgreSQL takes 3-5 seconds to accept connections after its container starts. Without health checks, your API container starts immediately, tries to connect to a PostgreSQL that is not ready yet, crashes, and goes into a restart loop.
A health check tells Docker how to verify a service is actually ready — not just started.
WITHOUT health check: postgres container starts api container starts immediately (depends_on: postgres) api tries DB connection -> connection refused api crashes -> restart loop WITH health check: postgres container starts postgres health check: pg_isready runs every 5s postgres becomes healthy (pg_isready returns 0) THEN api starts api connects successfullyHealth Check Configuration
services: postgres: image: postgres:15-alpine environment: POSTGRES_PASSWORD: secret healthcheck: test: ["CMD-SHELL", "pg_isready -U postgres"] interval: 5s # run every 5 seconds timeout: 3s # must complete within 3 seconds retries: 5 # mark unhealthy after 5 consecutive failures start_period: 10s # wait 10s before first check (startup grace period) api: depends_on: postgres: condition: service_healthy # wait until postgres is healthyHealth Check Commands for Common Services
# PostgreSQLhealthcheck: test: ["CMD-SHELL", "pg_isready -U postgres -d mydb"] # MySQLhealthcheck: test: ["CMD", "mysqladmin", "ping", "-h", "localhost"] # Redishealthcheck: test: ["CMD", "redis-cli", "ping"] # HTTP endpoint (requires curl in image)healthcheck: test: ["CMD", "curl", "-f", "http://localhost:8080/health"] # HTTP endpoint (wget — available in Alpine)healthcheck: test: ["CMD", "wget", "--no-verbose", "--tries=1", "--spider", "http://localhost:8080/health"] # RabbitMQhealthcheck: test: ["CMD", "rabbitmq-diagnostics", "ping"] start_period: 30s # RabbitMQ takes longer to start # MongoDBhealthcheck: test: ["CMD", "mongosh", "--eval", "db.adminCommand('ping')"]Container Health States
# Check health statusdocker compose ps# NAME STATUS# postgres Up 2m (healthy) <- passing health check# api Up 1m (healthy)# redis Up 2m (unhealthy) <- failing health check! # See health check output and failure reasondocker inspect redis-container \ --format '{{json .State.Health}}' | jq# {# "Status": "unhealthy",# "FailingStreak": 3,# "Log": [{# "ExitCode": 1,# "Output": "Could not connect to Redis at 127.0.0.1:6379"# }]# }TipAlways set
start_periodfor services that take time to initialise. Without it, health check failures during the normal startup window count toward the failure threshold and can mark the service as unhealthy before it even finished starting. Usestart_period: 10sfor databases andstart_period: 30sfor services like RabbitMQ or Elasticsearch.
Common MistakeUsing
depends_on: postgreswithoutcondition: service_healthy. Basicdepends_ononly waits for the container to start — not for PostgreSQL to accept connections. Your API will start and immediately try to connect to a database that needs 3-5 more seconds. Always add the health check condition.
Frequently Asked Questions
Why isn't a container's 'running' status enough to know it's ready?
A container can be in Running state while its application inside is still initializing, crashed into a hung loop, or listening but returning errors — Docker's process-level status doesn't inspect application health. A Compose health check runs an actual probe command (like curl against a health endpoint) on an interval, and only then reports the container as healthy, giving dependent services something meaningful to wait on instead of guessing based on a fixed sleep delay.
What's a common mistake with Compose health checks and depends_on?
Using plain `depends_on: [service]` without `condition: service_healthy`, which only waits for the dependency's container to start, not for it to actually be ready to accept connections — a database container starts almost instantly but may take several more seconds before it accepts connections. Also watch the health check's own timing: too short an interval or too few retries can mark a slow-starting service unhealthy before it's had a fair chance to come up.