Stage 4 · Provision
Networking, Storage & Compose Deep Dive
Compose Healthchecks
Write healthcheck commands, depends_on conditions, restart policies, and readiness gates for local stacks.
Healthcheck Syntax
services:
api:
image: myapp
healthcheck:
test: ["CMD", "curl", "-f", "http://localhost:3000/health"]
interval: 15s # Time between checks
timeout: 5s # Max time for a check
retries: 3 # Consecutive failures before unhealthy
start_period: 30s # Grace period for startup
start_interval: 5s # Checks during start period (Compose v2.29+)start_period gives the application time to initialize. Checks during this period do not count toward the retry limit. start_interval allows more frequent checks during startup.
# Shell command (exec form — no shell)
test: ["CMD", "curl", "-f", "http://localhost:3000/health"]
# Shell command (shell form — wraps in sh -c)
test: ["CMD-SHELL", "curl -f http://localhost:3000/health || exit 1"]
# Custom script
test: ["CMD", "/usr/local/bin/healthcheck.sh"]
# wget for images without curl
test: ["CMD-SHELL", "wget -qO- http://localhost:3000/health || exit 1"]
# Check TCP port
test: ["CMD-SHELL", "nc -z localhost 3000 || exit 1"]
# Disable healthcheck
test: ["NONE"]CMD form runs the command directly without a shell. CMD-SHELL wraps in sh -c, allowing shell features like || and pipes. Use CMD for simple commands, CMD-SHELL for complex ones.
depends_on Conditions
services:
api:
build: .
depends_on:
db:
condition: service_healthy
cache:
condition: service_healthy
command: node server.js
db:
image: postgres:16-alpine
environment:
POSTGRES_DB: app
POSTGRES_PASSWORD: secret
healthcheck:
test: ["CMD-SHELL", "pg_isready -U postgres"]
interval: 5s
timeout: 5s
retries: 10
cache:
image: redis:7-alpine
healthcheck:
test: ["CMD", "redis-cli", "ping"]
interval: 5s
timeout: 3s
retries: 5condition: service_healthy waits for the dependency's healthcheck to pass. The api service will not start until db and cache are healthy. This eliminates race conditions.
Restart Policies
services:
api:
image: myapp
restart: unless-stopped
# Always restart unless explicitly stopped
worker:
image: myapp-worker
restart: on-failure:5
# Restart on failure, max 5 times
db:
image: postgres:16-alpine
restart: always
# Always restart, even after daemon restart
adminer:
image: adminer:latest
restart: "no"
# Never restart (default)unless-stopped is the best default for long-running services. It restarts on crashes and host reboots but respects explicit docker stop commands.
Readiness Gates
services:
api:
build: .
depends_on:
db:
condition: service_healthy
healthcheck:
test: ["CMD-SHELL", "curl -f http://localhost:3000/ready || exit 1"]
interval: 10s
timeout: 5s
retries: 3
start_period: 60s
db:
image: postgres:16-alpine
healthcheck:
test: ["CMD-SHELL", "pg_isready -U postgres -d app"]
interval: 5s
timeout: 5s
retries: 10
redis:
image: redis:7-alpine
healthcheck:
test: ["CMD", "redis-cli", "-a", "password", "ping"]
interval: 5s
timeout: 3s
retries: 5The /ready endpoint verifies the application can serve requests, not just that the HTTP server is listening. pg_isready verifies PostgreSQL is accepting connections.
Service-Specific Healthchecks
services:
postgres:
image: postgres:16-alpine
healthcheck:
test: ["CMD-SHELL", "pg_isready -U postgres"]
interval: 10s
timeout: 5s
retries: 5
redis:
image: redis:7-alpine
healthcheck:
test: ["CMD", "redis-cli", "ping"]
interval: 10s
timeout: 3s
retries: 3
mongo:
image: mongo:7
healthcheck:
test: echo 'db.runCommand("ping").ok' | mongosh --quiet
interval: 10s
timeout: 5s
retries: 5
mysql:
image: mysql:8
healthcheck:
test: ["CMD", "mysqladmin", "ping", "-h", "localhost"]
interval: 10s
timeout: 5s
retries: 5
rabbitmq:
image: rabbitmq:3-management
healthcheck:
test: rabbitmq-diagnostics -q ping
interval: 10s
timeout: 5s
retries: 5Each service has its own health check mechanism. Use the native tools (pg_isready, redis-cli ping) rather than generic HTTP checks when possible.
Debugging Healthchecks
# Check service health status
docker compose ps
# View health check logs
docker inspect --format '{{json .State.Health}}' mycontainer | jq
# Run the health check command manually
docker exec mycontainer curl -f http://localhost:3000/health
# Check container logs for errors
docker compose logs api
# Force recreate unhealthy containers
docker compose up -d --force-recreate apiIf a service is unhealthy, first check the health check logs with docker inspect. Then run the health check command manually to debug. Finally, check the application logs for the root cause.
Applications that take time to initialize (Java, compiled languages) need generous start_period values. During this period, failed checks do not count toward the retry limit, giving the application time to start.
Mark this lesson complete to store local progress and unlock a cleaner resume path the next time you visit.