Skip to Content
DocsDockerMulti-containerHealth Checks & Restart

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:

  1. The container starts. Docker has launched the Postgres program. depends_on only waits for this moment.
  2. 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.

compose.yaml
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
  • test — the command Docker runs to check health. pg_isready is 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:

Terminal
docker compose ps

The 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:

compose.yaml
api: build: context: ./api environment: - DATABASE_URL=postgresql://appuser:secret@db:5432/appdb depends_on: db: condition: service_healthy

This 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.

compose.yaml
api: restart: unless-stopped
PolicyBehavior
no (default)Never restart automatically
alwaysAlways restart, even after an explicit manual stop, and when Docker itself restarts
unless-stoppedRestart automatically, unless you explicitly stopped it yourself
on-failureRestart 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

compose.yaml
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

  1. Add the health check to your db service, and switch api’s depends_on to the condition: service_healthy form.
  2. Run docker compose up and watch docker compose ps — confirm db shows healthy before api starts.
  3. Add restart: unless-stopped to all three services.
  4. Test it: run docker compose kill api to simulate a crash, then check docker compose ps again — 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_healthy changes about depends_on?
  • Can you explain the difference between restart: always and restart: unless-stopped?

Next → Compose Secrets

docker compose healthcheck, docker depends_on condition, docker restart policy, pg_isready docker compose

Last updated on