Docker Compose depends_on Not Waiting: Add a Healthcheck
If a Docker Compose app starts before its database is ready, the fix is usually not a longer sleep. Short-form depends_on establishes startup order, but a dependent service needs a real healthcheck and condition: service_healthy when it must wait for readiness.
Compose can then create the database first, wait for its healthcheck to pass, and only create the application service. The check must test the dependency’s real protocol, not merely whether its container process exists.
Why depends_on can look broken
The short syntax is useful for ordering:
services: web: image: example/web depends_on: - db
db: image: postgres:18It tells Compose that db is a dependency of web. It does not describe when the database can accept connections. A container can be running while PostgreSQL is still initializing, migrations are pending, or an application-specific readiness condition has not been met.
The Compose startup-order documentation defines three useful dependency conditions:
service_started: the dependency has started.service_healthy: the dependency’shealthcheckhas passed.service_completed_successfully: the dependency ran to completion with exit code zero.
Use the condition that matches the contract. Do not use service_healthy without defining what healthy means.
Add a healthcheck that tests readiness
For PostgreSQL, a minimal readiness gate can look like this:
services: web: build: . depends_on: db: condition: service_healthy restart: true
db: image: postgres:18 environment: POSTGRES_USER: app POSTGRES_PASSWORD: example POSTGRES_DB: app healthcheck: test: ["CMD-SHELL", "pg_isready -U $${POSTGRES_USER} -d $${POSTGRES_DB}"] interval: 10s timeout: 10s retries: 5 start_period: 30sThe official example uses pg_isready and escapes the dollar signs as $${...} so Compose passes the variable expression into the container instead of interpolating it too early. The command, credentials, and database name must match the image and application you actually run.
For Redis, an HTTP service, or a custom worker, replace the probe with a command that is available in that image and checks the service’s useful boundary. A TCP port being open may still be weaker than a protocol-level check.
Choose the right dependency condition
Use service_started for ordering only
Use this when the dependent service can retry its connection and does not require the dependency to be ready before its own process starts. It is also the behavior represented by the simple list syntax.
Use service_healthy for a readiness gate
Use this when the application should not start until a healthcheck passes. The Compose Specification guarantees the dependency order and the health condition, but your healthcheck still has to be correct and eventually become healthy.
Use service_completed_successfully for one-shot jobs
A migration or seed container can run before the web service:
services: web: build: . depends_on: migrate: condition: service_completed_successfully
migrate: build: . command: ./bin/migrate restart: "no"Keep the migration command idempotent and make its exit code meaningful. A job that exits zero before doing the required work creates a false readiness signal.
Debug the rendered model, then the health state
Check the Compose model before debugging the application:
docker compose config --servicesdocker compose config --quietdocker compose up -d dbdocker compose psIf the database is not healthy, inspect its logs and health state instead of restarting the web container repeatedly:
docker compose logs dbdocker inspect --format '{{json .State.Health}}' PROJECT-db-1The exact container name varies by project name and Compose version. docker compose ps gives you the name to inspect. Also check these common mistakes:
- The healthcheck binary is not installed in the image.
- The probe uses
localhostwhen the real client connects over the Compose service name. - The port or credentials in the probe do not match the container environment.
start_periodis shorter than the dependency’s normal initialization time.- The dependency becomes healthy, then loses its connection later; a startup gate does not replace application retries.
If the service is inside a profile, activate the same profile while rendering and starting the model. The existing Compose profile troubleshooting guide covers profile activation; this article covers readiness after the service is selected.
FAQ
Does depends_on wait for a database to be ready?
Only when the dependency uses a healthcheck and the long syntax sets condition: service_healthy. The short syntax orders container startup but cannot know database readiness.
Why is my service still starting too early with service_healthy?
Inspect the healthcheck itself. Confirm the command exists in the image, the endpoint is correct, and docker compose ps reports the dependency as healthy before the dependent service is created.
Should I add sleep 30 instead?
No. A fixed delay is slower when startup is fast and too short when startup is slow. A protocol-level healthcheck expresses the condition Compose actually needs to wait for.
References:
Report a typo or broken link, or suggest a related topic.