All posts

Dockerfile Health Check: Copy-Paste Recipes for Every Stack

August 22, 2026

A Dockerfile health check tells the Docker daemon whether a container is actually working, not just running. A process can stay alive while its event loop is deadlocked, its database pool is exhausted, or its disk is full — and without a HEALTHCHECK instruction, Docker has no way to know. This guide gives you working HEALTHCHECK recipes for common stacks, then draws a hard line around what this feature can and cannot do — because that line matters once scheduled jobs enter the picture.

How Docker's HEALTHCHECK Instruction Actually Works

The HEALTHCHECK instruction runs a command inside the container on a fixed interval. If it exits 0, Docker marks the container healthy. Nonzero, and it's unhealthy. Three consecutive failures (by default) flip the status, which then shows up in docker ps and can trigger restarts under orchestrators like Docker Swarm or be read by tools polling container state.

The instruction takes several options worth tuning deliberately rather than leaving at defaults:

  • --interval — how often to run the check (default 30s)
  • --timeout — how long to wait before considering the check failed (default 30s)
  • --start-period — a grace window during startup where failures don't count against the retry limit
  • --retries — consecutive failures required to flip to unhealthy (default 3)

A slow-starting Java service or a database doing initial migrations needs a generous --start-period; skip it and you'll get flapping health states during every deploy.

Dockerfile Health Check Recipes for Common Stacks

Web Applications (Node.js, Python, Go)

Most HTTP services should expose a lightweight /health or /healthz endpoint that checks the process can respond — not a full dependency check (more on that distinction below).

HEALTHCHECK --interval=30s --timeout=3s --start-period=10s --retries=3 \
  CMD curl -f http://localhost:3000/health || exit 1

If your base image doesn't include curl, use wget:

HEALTHCHECK --interval=30s --timeout=3s --retries=3 \
  CMD wget --no-verbose --tries=1 --spider http://localhost:3000/health || exit 1

For minimal images without either binary (distroless, Alpine without extras), write a tiny script in the app's own language instead:

HEALTHCHECK --interval=30s --timeout=3s --retries=3 \
  CMD node healthcheck.js || exit 1

PostgreSQL

Postgres images ship pg_isready, which checks that the server accepts connections without needing credentials:

HEALTHCHECK --interval=10s --timeout=5s --start-period=15s --retries=5 \
  CMD pg_isready -U ${POSTGRES_USER} -d ${POSTGRES_DB} || exit 1

MySQL / MariaDB

HEALTHCHECK --interval=10s --timeout=5s --start-period=15s --retries=5 \
  CMD mysqladmin ping -h localhost -u root -p${MYSQL_ROOT_PASSWORD} || exit 1

Redis

HEALTHCHECK --interval=10s --timeout=3s --retries=3 \
  CMD redis-cli ping | grep -q PONG || exit 1

RabbitMQ

RabbitMQ includes a purpose-built diagnostic command that checks more than just TCP reachability:

HEALTHCHECK --interval=15s --timeout=10s --start-period=30s --retries=3 \
  CMD rabbitmq-diagnostics -q ping || exit 1

Kafka

Kafka has no built-in ping, so check that the broker responds to a basic topic listing:

HEALTHCHECK --interval=15s --timeout=10s --start-period=30s --retries=5 \
  CMD kafka-broker-api-versions.sh --bootstrap-server localhost:9092 || exit 1

Liveness vs. Readiness: Don't Conflate Them

A container can be alive (the process hasn't crashed) without being ready (able to serve traffic correctly). Your /health endpoint should reflect liveness — is the process responsive at all — while a separate /ready endpoint can check downstream dependencies like database connectivity. Baking a full dependency check into your Docker health check means one flaky database connection restarts a perfectly fine application container, which is rarely what you want. Keep the Dockerfile health check narrow and fast; push deeper dependency logic into your orchestration layer or a dedicated readiness probe.

What Docker HEALTHCHECK Can't Do

This is the part most tutorials skip. HEALTHCHECK is built around one assumption: there's a long-running process inside the container that you can repeatedly probe. That assumption holds for web servers, databases, and queues. It falls apart for anything that runs, finishes, and exits — which describes most scheduled work.

A cron job, a nightly billing script, a cache-warming task triggered by a scheduler, a webhook-triggered batch process — none of these have a process sitting around to answer a health probe between runs. The container starts, does its job, and terminates. There's nothing to poll at 2:47 AM when the job isn't scheduled to run until 3:00 AM, and no mechanism in docker ps that tells you the job that was supposed to run three hours ago never happened at all.

That's a structurally different failure mode: not "the process is unhealthy" but "the process never ran, ran too slowly, or ran and silently failed after exit code 0 got swallowed somewhere in a pipeline." Docker's health check model has no concept of execution history, no alerting on missed schedules, and no visibility once the container has already exited.

Monitoring Scheduled and Cron Jobs Docker Can't See

This is where a Docker health check needs a companion, not a replacement. Cronevra fills that gap by tracking scheduled and cron-triggered HTTP jobs directly — recording each execution, timing it against the expected schedule, and flagging failures or no-shows so you're not the one who discovers a missed job three days later when a report never arrived. Instead of grepping logs across containers or wiring a custom heartbeat mechanism into every script, your job pings Cronevra on start and completion; if the ping is late or missing, you get an alert.

The two approaches aren't competing — they cover different halves of your infrastructure. HEALTHCHECK watches things that stay running. Cronevra watches things that run on a schedule and then go away. If your stack has both a web API and a fleet of scheduled tasks — nearly every production system — you need visibility into both.

Frequently Asked Questions

What does the Dockerfile HEALTHCHECK instruction actually check?

It runs a command you specify, on an interval you configure, and marks the container healthy or unhealthy based on the exit code. It only tells you the process inside a running container is responsive — it says nothing about jobs that start, finish, and exit, like cron tasks.

Why does my container show "unhealthy" right after startup?

This usually means your --start-period is too short for the application's actual boot time. Failures during the start period don't count toward the retry threshold, so lengthening it (e.g., to 30–60s for a database or JVM app) fixes false negatives during deploys.

Can Docker HEALTHCHECK monitor cron jobs?

No — HEALTHCHECK requires a long-running process to probe repeatedly, and a cron job's container exits once the task finishes. To catch missed runs, silent failures, or jobs that never started, you need a dedicated monitor like Cronevra that tracks execution history against the expected schedule.

Should my health check test the database connection too?

Generally no. A narrow liveness check (is the process responsive?) belongs in HEALTHCHECK, while dependency checks belong in a separate readiness endpoint. Mixing them means a temporary database blip can trigger unnecessary container restarts.

What's the difference between HEALTHCHECK and a readiness probe in Kubernetes?

Docker's HEALTHCHECK is a container-level instruction read by the Docker daemon; a Kubernetes readiness probe is an orchestrator-level concept that decides whether a pod receives traffic. They can point at the same endpoint, but readiness probes offer more granular control over traffic routing that Docker alone doesn't provide.

How do I get alerted if a scheduled HTTP job silently fails?

Set the job to ping a monitoring endpoint on start and completion so a missing or late ping triggers an alert automatically. Cronevra is built specifically for this — it tracks execution history for scheduled and cron-triggered jobs and alerts you the moment one fails or doesn't run, which is outside what Docker's HEALTHCHECK can ever see.

Docker's HEALTHCHECK instruction is the right tool for containers that stay alive and need continuous verification — copy the recipes above, tune the timing options for your stack, and keep liveness and readiness logic separate. But once your infrastructure includes anything scheduled — a nightly job, a periodic sync, a cron-triggered webhook — you're past what HEALTHCHECK was designed for. Cronevra covers that other half: real execution history, missed-run detection, and recovery alerts for the jobs Docker was never built to watch.