docker-compose-depends-on-healthchecks

Dependencies and Startup Order in Docker Compose

  • 4 min

The Healthchecks in Docker are diagnostic tests that allow the engine to verify if a container is running and if the application inside is ready.

Imagine you have written your docker-compose.yml. You have your Backend and your database. You run docker compose up and… your Backend exits with an error.

You check the logs and see:

Connection Refused: Cannot connect to database

How is that possible? But you used depends_on. The database should have been available… clearly not (otherwise, I wouldn’t be writing this article).

Today we are going to uncover why depends_on alone is not sufficient, and we will learn how to use the tool to create ordered startups: Healthchecks 👇.

The Lie of depends_on

When talking about Docker Compose, we saw the depends_on instruction, which is used to start some containers after others.

depends_on only waits for the container to start, not for it to be ready.

The problem is that starting a container does not mean the software inside it is ready.

When we write this in our YAML file:

services:
  web:
    image: my-app
    depends_on:
      - db
  db:
    image: postgres
Copied!

We tell Docker to start the db container first. As soon as the process has started, start web.

But a database can take 10 or 20 seconds to actually initialize. It needs to load files, verify integrity, open sockets…

During those seconds, the container is “running” (Up), but it is not ready to accept connections.

Your web application, which is much faster to start (say, 1 second), tries to connect, the database rejects it (because it’s still loading), and your application dies.

The Solution: Healthchecks

To fix this, we need Docker to know the difference between “I am on” and “I am ready to work”.

This is done by defining a Healthcheck. Basically, a command that Docker will run repeatedly inside the container to ask: Are you ready?.

Step 1️⃣: Define the Healthcheck on the DB

Let’s add the healthcheck section to our database service. The command will depend on the software (Postgres, MySQL, Redis…).

Let’s assume an example with PostgreSQL:

services:
  db:
    image: postgres:15
    environment:
      POSTGRES_PASSWORD: secret
    healthcheck:
      test: ["CMD-SHELL", "pg_isready -U postgres"]
      interval: 5s
      timeout: 5s
      retries: 5
Copied!

What does this mean?

  1. test: Runs the pg_isready command (specific to Postgres) inside the container.
  2. interval: Do this every 5 seconds.
  3. retries: If it fails 5 times in a row, mark the container as unhealthy.

Now, if you run docker ps, you will see something new in the STATUS column: Up 10 seconds (health: starting) and then (healthy).

Step 2️⃣: Conditional Waiting

Now we need to tell our web application not to start just when the DB starts, but when the DB is ready.

We change the short syntax of depends_on for the long syntax:

services:
  web:
    image: my-app
    depends_on:
      db:
        condition: service_healthy  # <--- The new part!
Copied!

With this, Docker Compose will do the following:

  1. Start db.
  2. Wait. Run the healthcheck… fails… wait…
  3. Run the healthcheck… Success! The status changes to healthy.
  4. Start web.

Complete Robust Stack Example

Here is a bulletproof docker-compose.yml you could use in production:

version: '3.8'

services:
  # Database
  database:
    image: mysql:5.7
    environment:
      MYSQL_ROOT_PASSWORD: root
      MYSQL_DATABASE: app_db
    volumes:
      - db_data:/var/lib/mysql
    healthcheck:
      test: ["CMD", "mysqladmin", "ping", "-h", "localhost"]
      interval: 5s
      timeout: 10s
      retries: 5

  # Web Application
  webapp:
    image: my-web-image:latest
    ports:
      - "80:80"
    environment:
      DB_HOST: database
    depends_on:
      database:
        condition: service_healthy
    restart: on-failure

volumes:
  db_data:
Copied!