Skip to main content

Docker Health Check

A Dockerfile instruction or docker run flag that defines a command Docker runs periodically inside a container to determine if it is healthy. Containers can be starting, healthy, or unhealthy — used by orchestrators for traffic routing decisions.

Docker Health Check — Is Your Container Actually Working?

What Is a Docker Health Check in Simple Terms?

A container can be running without actually working. PostgreSQL might be up but still initialising. Your API might be running but stuck in a deadlock. A health check is a command Docker runs periodically inside the container — if it succeeds (exit code 0), the container is healthy. If it fails repeatedly, Docker marks it as unhealthy.

Orchestrators use health status for routing — Kubernetes does not send traffic to pods that are not healthy.

◈ DIAGRAM
+------------------------------------------+
| Container starts |
| Status: starting |
+------------------------------------------+
| (after start_period)
|
| health check runs (every interval)
|
v
+------------------------------------------+
| Exits 0 repeatedly -> healthy |
| Exits non-0 repeatedly -> unhealthy |
+------------------------------------------+

HEALTHCHECK in Dockerfile

Dockerfile
# HTTP endpoint check
HEALTHCHECK --interval=30s --timeout=5s \
--start-period=15s --retries=3 \
CMD curl -f http://localhost:8080/health || exit 1
# wget (available in Alpine, no curl needed)
HEALTHCHECK --interval=30s --timeout=5s --retries=3 \
CMD wget --no-verbose --tries=1 --spider \
http://localhost:8080/health || exit 1
# Parameters:
# --interval How often to run (default: 30s)
# --timeout Max time for check to complete (default: 30s)
# --start-period Grace period before first check (default: 0s)
# --retries Failures before unhealthy (default: 3)

Health Check Commands by Service

Dockerfile
# PostgreSQL
HEALTHCHECK CMD pg_isready -U postgres -d mydb || exit 1
# Redis
HEALTHCHECK CMD redis-cli ping | grep -q PONG || exit 1
# MySQL
HEALTHCHECK CMD mysqladmin ping -h localhost || exit 1
# RabbitMQ
HEALTHCHECK --start-period=30s \
CMD rabbitmq-diagnostics ping || exit 1
# Custom application check
HEALTHCHECK CMD node /app/scripts/healthcheck.js || exit 1

Monitoring Health Status

Bash
# See health status in docker ps
docker ps
# NAME STATUS
# payment-api Up 5m (healthy)
# postgres Up 5m (healthy)
# redis Up 2m (unhealthy) <- problem!
# See health check logs for unhealthy container
docker inspect redis \
--format '{{json .State.Health}}' | jq
# {
# "Status": "unhealthy",
# "FailingStreak": 3,
# "Log": [{
# "ExitCode": 1,
# "Output": "Could not connect to Redis"
# }]
# }
# The Output field tells you exactly why the check is failing
# In Docker Compose — use with depends_on
services:
api:
depends_on:
postgres:
condition: service_healthy # waits for healthy status
Tip

The start_period parameter is critical for services that take time to initialise. Set it to the maximum time your service needs to start — typically 10s for web services and 30s for databases. Without start_period, health check failures during normal startup count toward the failure threshold and can mark healthy services as unhealthy.

Frequently Asked Questions

How does a Docker health check differ from just checking if the container process is still running?

Docker's default notion of 'running' only means the main process hasn't exited — it says nothing about whether the app inside is actually serving traffic correctly. A HEALTHCHECK instruction runs a real command (like `curl -f http://localhost/health`) on an interval and tracks state transitions through starting, healthy, and unhealthy, which is critical for apps that can hang, deadlock, or lose a database connection while the process itself stays alive.

What's a common health check mistake that causes false 'unhealthy' flapping?

Setting the check interval or timeout too aggressively for an app with variable response times under load, so a healthy-but-momentarily-slow container gets marked unhealthy and cycled out of rotation, adding load to remaining containers and making the situation worse. Also common: forgetting `start_period`, which gives slow-starting apps (e.g. those doing cache warmup) a grace period before failed checks count against the retry threshold — without it, a normal cold start can be misread as a crash loop.