Skip to content
Community

EXPLORE TOPICS

Editorial approach

New to CoreTrailKit?

Observe your services. Keep the evidence close.

CoreTrailKit
Back to articles
GUIDE/ Docker

Docker Compose: wait for a healthy dependency

The database container is running, yet the API fails on its first connection. Restarting only the API makes the stack work. Before adding a longer sleep, test whether the dependency was accepting requests when the client started.

By CoreTrailKit4 min read

01

Recognize a first-start race

A useful clue is the difference between a cold start and a manual retry. The API exits immediately during the first compose up, but starts successfully a few seconds later against the same address. Record the first failure and the dependency startup logs before changing configuration.

Check that the hostname and port are already correct. A request to localhost from the API container still targets that container; waiting for readiness cannot repair that separate networking mistake. This investigation assumes the API is using the dependency service name.

02

Separate running from ready

Container creation, process startup, socket listening, and a usable application are different milestones. In our lab the dependency process starts immediately, waits twelve seconds, then binds its HTTP socket. The client makes one request during that delay and gets a connection failure.

A short depends_on list orders the services but does not perform this readiness request. Inspect the actual container and its logs rather than interpreting an Up label as a successful database handshake.

sh · EXAMPLE
docker compose ps --all
docker compose logs --timestamps --tail=100 api db
# Select the dependency container, then inspect its health record.
db_id=$(docker compose ps --all --quiet db)
docker inspect --format '{{json .State.Health}}' "$db_id"

03

Gate the first request on a real check

For a PostgreSQL dependency, the following is a configuration pattern rather than a complete application. Replace the application image with your own, and supply its database connection settings separately. The database password comes from the local environment; use a secret mechanism appropriate to your deployment.

Choose the timing from measured startup behavior. The doubled dollar signs leave expansion of POSTGRES_USER and POSTGRES_DB to the shell inside the database container. pg_isready checks server readiness; it does not prove that migrations have finished or that your application role can run its required query.

yaml · EXAMPLE
services:
  db:
    image: postgres:16
    environment:
      POSTGRES_USER: app
      POSTGRES_DB: app
      POSTGRES_PASSWORD: ${DB_PASSWORD:?Set DB_PASSWORD}
    healthcheck:
      test: ["CMD-SHELL", "pg_isready -U $${POSTGRES_USER} -d $${POSTGRES_DB}"]
      interval: 2s
      timeout: 2s
      retries: 20
      start_period: 5s
  api:
    image: your-api:dev
    depends_on:
      db:
        condition: service_healthy

04

Verify the change without deleting data

Validate the resolved Compose model before starting it. Recreate only the containers required by the configuration change, observe the health transition, and compare the timestamps with the API connection attempt. Do not remove a database volume merely to make startup look clean.

Our HTTP reproduction failed with the short dependency list and returned HTTP 200 with service_healthy. Both variants used the same twelve-second startup delay and the same client request. The changed variable was the readiness gate, not a different network or a new database.

sh · EXAMPLE
docker compose config --quiet
docker compose up --detach
docker compose ps
docker compose logs --timestamps --tail=100 api db

05

Handle the next outage separately

After the API has started, the database can restart or temporarily stop accepting work. A startup gate is not a permanent recovery controller. Make connection retries bounded, surface persistent failures, and ensure repeated operations cannot create duplicate work.

If the API still fails after the dependency is healthy, continue with authentication, schema state, and the exact failed operation. A healthy readiness probe narrows the investigation; it does not establish that the entire application is healthy.

Know the limits

This is a controlled reproduction of a startup race, not a claim about a customer outage. The runnable lab uses Python 3.12 HTTP processes to separate container startup from readiness; a production database needs its own appropriate check. Applies to Docker Compose, not automatically to every orchestrator. Reproduction checked on 26 October 2026 using Linux containers on Docker Engine 29.6.2 and Compose 5.3.1; this is the validation environment, not a minimum-version claim.

Sources & further reading

A technical publication by CoreTrailKit.

Browse all articles

CONTINUE EXPLORING