Health Checks & Restart
Two small additions make your stack much more reliable: checking that a service is truly ready, not just started, and telling Docker to restart a crashed container automatically.
The Gap depends_on Leaves
There are two separate moments when db starts:
- The container starts. Docker has launched the Postgres program.
depends_ononly waits for this moment. - Postgres becomes ready. After starting, Postgres still needs a short time to set up its files and its database. Only after this is it ready to accept connections.
Between these two moments, there is a short gap — usually a few seconds. During this gap, the container is running, but Postgres cannot accept connections yet.
depends_on only waits for moment 1, not moment 2. Most of the time, api happens to start slowly enough that moment 2 has already passed by the time it tries to connect, so nothing goes wrong. Occasionally, api starts fast enough to hit that gap, and its first connection attempt fails.
Health Checks
A health check is a command. Docker runs it again and again, inside the container. It checks if the service truly works — not just if it is running.
db:
image: postgres:16-alpine
environment:
- POSTGRES_USER=appuser
- POSTGRES_PASSWORD=secret
- POSTGRES_DB=appdb
volumes:
- db-data:/var/lib/postgresql/data
healthcheck:
test: ["CMD-SHELL", "pg_isready -U appuser"]
interval: 5s
timeout: 5s
retries: 5test— the command Docker runs to check health.pg_isreadyis a small tool built into the Postgres image just for this.interval— how often Docker runs the check.timeout— how long Docker waits for one check to finish, before calling it failed.retries— how many failed checks in a row, before Docker marks the service “unhealthy”.
Check it directly:
docker compose psThe STATUS column now shows healthy once the check passes, not just Up.
Making depends_on Wait for Health
Once a health check exists, depends_on can wait for real readiness, not just container start:
api:
build:
context: ./api
environment:
- DATABASE_URL=postgresql://appuser:secret@db:5432/appdb
depends_on:
db:
condition: service_healthyThis is the same depends_on key as before, just written in a longer form. Now api waits until db is healthy — not just started. This closes the gap explained above.
Restart Policies
Restart policy is a different setting, not related to startup order. It tells Docker what to do if a container crashes, or if your computer restarts.
api:
restart: unless-stopped| Policy | Behavior |
|---|---|
no (default) | Never restart automatically |
always | Always restart, even after an explicit manual stop, and when Docker itself restarts |
unless-stopped | Restart automatically, unless you explicitly stopped it yourself |
on-failure | Restart only if the container exited with an error |
unless-stopped is a good default for most services. It restarts after a crash. But it will not restart if you stop it yourself on purpose, for maintenance.
Putting It Together
services:
web:
build:
context: ./web
ports:
- "3000:3000"
environment:
- API_URL=http://api:4000
depends_on:
- api
restart: unless-stopped
api:
build:
context: ./api
ports:
- "4000:4000"
environment:
- DATABASE_URL=postgresql://appuser:secret@db:5432/appdb
depends_on:
db:
condition: service_healthy
restart: unless-stopped
db:
image: postgres:16-alpine
environment:
- POSTGRES_USER=appuser
- POSTGRES_PASSWORD=secret
- POSTGRES_DB=appdb
volumes:
- db-data:/var/lib/postgresql/data
healthcheck:
test: ["CMD-SHELL", "pg_isready -U appuser"]
interval: 5s
timeout: 5s
retries: 5
restart: unless-stopped
volumes:
db-data:web still depends on api using the plain form, not condition: service_healthy. That condition only works if api has its own healthcheck: — and this stack does not define one for it. Express starts fast enough that this gap rarely matters in practice, but the same healthcheck: block shown for db could be added to api the same way, if you needed that guarantee too.
Hands-on Task
- Add the health check to your
dbservice, and switchapi’sdepends_onto thecondition: service_healthyform. - Run
docker compose upand watchdocker compose ps— confirmdbshowshealthybeforeapistarts. - Add
restart: unless-stoppedto all three services. - Test it: run
docker compose kill apito simulate a crash, then checkdocker compose psagain — it should already be starting back up.
Quick Check
- Can you explain the difference between a container “running” and a service being “healthy”?
- Do you know what
condition: service_healthychanges aboutdepends_on? - Can you explain the difference between
restart: alwaysandrestart: unless-stopped?
Next → Compose Secrets