Docker Compose’s depends_on directive doesn’t actually wait for services to be ready; it just waits for their containers to start. This means your application can try to connect to a database that’s still initializing, leading to frustrating startup errors.

Let’s see this in action. Imagine a simple docker-compose.yml for a web app and a database:

version: '3.8'
services:
  webapp:
    build: .
    ports:
      - "8000:8000"
    depends_on:
      - db
  db:
    image: postgres:14-alpine
    environment:
      POSTGRES_DB: mydb
      POSTGRES_USER: user
      POSTGRES_PASSWORD: password

If you run docker-compose up, the webapp container will start immediately after the db container begins running, not after PostgreSQL is fully initialized and ready to accept connections. Your webapp will likely fail to connect to db because the database is still spinning up.

The real solution is to leverage Docker’s healthcheck feature. A healthcheck defines a command that Docker periodically runs inside a container to determine its status. When a service has a healthcheck, depends_on will wait for that service to report as healthy before starting dependent services.

Here’s how to add a healthcheck to our db service:

version: '3.8'
services:
  webapp:
    build: .
    ports:
      - "8000:8000"
    depends_on:
      db:
        condition: service_healthy # This is the key!
  db:
    image: postgres:14-alpine
    environment:
      POSTGRES_DB: mydb
      POSTGRES_USER: user
      POSTGRES_PASSWORD: password
    healthcheck:
      test: ["CMD-SHELL", "pg_isready -U user -d mydb"]
      interval: 5s
      timeout: 5s
      retries: 5
      start_period: 10s

Let’s break down the healthcheck section:

  • test: This is the command that Docker runs. pg_isready is a PostgreSQL utility that checks if the server is ready to accept connections. We specify the user and database to ensure it’s checking the correct instance.
  • interval: How often Docker should run the test command. 5s means every 5 seconds.
  • timeout: How long Docker will wait for the test command to complete. If it takes longer than 5s, the healthcheck fails for that interval.
  • retries: The number of consecutive failures before Docker marks the container as unhealthy. 5 retries means it needs to fail 5 times in a row.
  • start_period: A grace period after the container starts. During this time, failing healthchecks are not counted towards the retries. This is crucial because a database takes time to initialize on its first run. 10s gives PostgreSQL a bit of breathing room.

Notice the change in depends_on for the webapp: condition: service_healthy. This tells Docker Compose to wait until the db service reports as healthy before starting the webapp container.

When you run docker-compose up with this configuration, you’ll observe that the webapp container won’t start until the db container’s healthcheck passes consistently. This guarantees that your application only attempts to connect when the database is truly ready.

The start_period is particularly important for databases and other services that have a significant initialization phase. Without it, a database might fail its initial healthchecks repeatedly, even if it’s on its way to becoming ready, and eventually be marked unhealthy, preventing your application from starting. The start_period allows the service to go through its initial setup without failing the healthcheck, and only starts counting failures after this initial period.

The pg_isready command itself exits with a status of 0 if the server is ready and 1 if it’s not. Docker interprets a 0 exit code as a healthy state and any non-zero exit code as an unhealthy state. This simple exit code mechanism is what powers Docker’s healthchecking for most services.

The next logical step is to consider how to handle multiple dependencies, or dependencies that themselves have dependencies, and ensuring the entire chain is healthy before your primary service starts.

Want structured learning?

Take the full Docker course →