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
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
What does this mean?
- test: Runs the
pg_isreadycommand (specific to Postgres) inside the container. - interval: Do this every 5 seconds.
- 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!
With this, Docker Compose will do the following:
- Start
db. - Wait. Run the healthcheck… fails… wait…
- Run the healthcheck… Success! The status changes to
healthy. - 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:
