DEV Community

Peon Sh
Peon Sh

Posted on Originally published at peon.sh

Health Checks in Docker Compose: Startup Order and Self-Healing

Write health checks that reflect real readiness, gate dependent services on them, and enable automatic recovery from wedged states.

Why "running" is not "working"
Docker knows whether your process is alive; it has no idea whether it works. A container can be "Up 3 hours" while the app inside deadlocked two hours ago, and nothing will restart it because, from the runtime’s perspective, everything is fine. Health checks close that gap: a command Docker runs inside the container on an interval, whose exit code declares healthy or unhealthy. That one bit of truth powers startup ordering, zero-downtime deploys and self-healing.

Anatomy of a good health check
start_period is the underused one: without it, slow-booting apps get marked unhealthy during normal startup
CMD-SHELL variant with a fallback survives slim images: curl -fsS URL || wget -q --spider URL, some images ship one tool but not the other

healthcheck:
test: ["CMD", "curl", "-fsS", "http://localhost:3000/health"]
interval: 10s # how often to probe
timeout: 5s # how long one probe may take
retries: 3 # consecutive failures before "unhealthy"
start_period: 30s # grace window at boot; failures don't count yet

Check readiness, not existence
The endpoint behind the check should verify the app can actually serve: process responsive and critical dependencies reachable (a cheap database ping). Return 200 only then. Keep it fast and unauthenticated, it runs every few seconds forever. For databases and infrastructure, use the purpose-built tools instead of HTTP:
postgres: test: ["CMD-SHELL", "pg_isready -U app"]
mysql: test: ["CMD", "mysqladmin", "ping", "-h", "localhost"]
redis: test: ["CMD", "redis-cli", "ping"]

Gate startup ordering on health
Plain depends_on orders container starts, which is nearly useless: Postgres "started" is seconds away from Postgres "accepting connections", and apps that connect in that window crash. The condition form waits for actual readiness and retires the whole boot-race class of bugs:

depends_on:
postgres:
condition: service_healthy # wait for the healthcheck, not the start

What health unlocks operationally

  • Zero-downtime deploys: the platform only switches traffic to a new container after its check passes; a bad release aborts instead of going live, this is how Peon gates rollouts

  • Self-healing: pair checks with restart policies (or a platform monitor) so a wedged-but-running container gets replaced instead of serving errors for hours

  • Truthful dashboards: service status reflects ability to serve, not merely process existence

  • One habit: define a healthcheck on every service that has dependents or takes traffic, it is ten lines that upgrade the reliability of everything built on top

Top comments (0)