Docker Compose Startup Order and Healthchecks - 夜莺博客

Docker Compose Startup Order and Healthchecks

The most common Compose failure is not a broken image - it is an application container that starts, cannot reach the database, and exits. Plain depends_on is the culprit: it waits for the dependency's container to be running, which says nothing about whether the process inside is ready to accept connections. Fixing it takes a healthcheck and the long-form dependency syntax, after which the order is deterministic.

The failure mode

api:
  image: myorg/api:latest
  depends_on:
    - db

Compose creates and starts db, then immediately starts api. Postgres is still initialising its data directory, so api gets connection refused, crashes, and the familiar exit code 1 appears. Compose did exactly what it promised; the promise was too weak.

Gate on readiness, not on start

services:
  web:
    build: .
    depends_on:
      db:
        condition: service_healthy
      redis:
        condition: service_started
    restart: true
  redis:
    image: redis
  db:
    image: postgres:18
    healthcheck:
      test: ["CMD-SHELL", "pg_isready -U $${POSTGRES_USER} -d $${POSTGRES_DB}"]
      interval: 10s
      retries: 5
      start_period: 30s
      timeout: 10s

The condition field is the whole fix:

  • service_started - the container is running (the old behaviour).
  • service_healthy - the dependency's healthcheck has reported healthy.
  • service_completed_successfully - the dependency exited with code 0, which is exactly what you want for one-shot migrations or seed jobs.

start_period is worth tuning: it tells Docker how long the service may legitimately fail its healthcheck during startup, so a slow first boot does not mark the container unhealthy prematurely. restart: true ensures that restarting or updating the dependency also restarts dependents, so they re-establish connections instead of holding dead sockets.

Verify that the ordering is real

docker compose up -d
docker compose ps
docker inspect --format '{{.State.Health.Status}}' compose-db-1
docker compose logs -f web

In the first seconds after up, db should show (health: starting) while web is not yet created - that is the gate working. Anything other than healthy from the inspect command means dependents are still being held back.

Pitfalls worth knowing

  • A healthcheck that always passes. A test that returns success because the binary is missing (curl is absent in slim images) is worse than no healthcheck. Use CMD-SHELL with a tool the image actually has, and check the container's health status in ps.
  • Using Compose as the retry mechanism. Ordering gets services started in the right sequence, not kept connected. Applications should still reconnect with backoff and survive a database restart mid-request.
  • Ignoring shutdown order. Compose stops containers in reverse dependency order, so the application is stopped before its database - which is what you want, but only if dependencies are declared.
  • Migrations as part of the app. A separate one-shot service with service_completed_successfully makes the schema step explicit and observable instead of hiding it in application startup.
  • Environment interpolation. Escape $ as $$ so variables are resolved inside the container at runtime rather than by Compose at parse time.

Related: Docker networking drivers explained, overlay2 disk full cleanup, and nginx stream module load balancing.

原文链接:https://docs.docker.com/compose/how-tos/startup-order