Short answer. The HEALTHCHECK instruction in a Dockerfile tells Docker how to verify that the app inside the container is genuinely alive, not just "process started." Docker periodically runs a command (usually curl to a health endpoint) and assigns the container a healthy or unhealthy status. Orchestrators use that status for restarts and load balancing. But a healthcheck lives inside the host — external availability is watched by synthetic monitoring on top.
Why you need HEALTHCHECK
Without a healthcheck, Docker considers a container "running" as long as the main process is alive. But that process can be deadlocked, unresponsive, or stuck in an endless GC — to Docker it is still "running." HEALTHCHECK adds a check at the application level.
- healthy — the command returned 0, the app responds;
- unhealthy — the command failed
retriestimes in a row; - starting — the container is within
start-period, still warming up; - none — no healthcheck is defined.
HEALTHCHECK in a Dockerfile
A basic example for a web app with a health endpoint. curl with the -f flag returns a non-zero code on 4xx/5xx, which Docker counts as a failure.
FROM node:20-alpine
WORKDIR /app
COPY package*.json ./
RUN npm ci --omit=dev
COPY . .
EXPOSE 8080
HEALTHCHECK --interval=30s --timeout=5s --start-period=20s --retries=3 \
CMD curl -fsS http://localhost:8080/healthz || exit 1
CMD ["node", "server.js"]
Healthcheck parameters
| Parameter | Purpose | Typical value |
|---|---|---|
| --interval | Period between checks | 30s |
| --timeout | How long to wait for a reply | 5s |
| --start-period | Grace time for startup | 20–60s |
| --retries | Consecutive failures before unhealthy | 3 |
Healthcheck in docker-compose
In compose the same healthcheck is declared declaratively, and other services can wait for the healthy status via depends_on with a condition.
services:
web:
build: .
ports:
- "8080:8080"
healthcheck:
test: ["CMD", "curl", "-fsS", "http://localhost:8080/healthz"]
interval: 30s
timeout: 5s
start_period: 20s
retries: 3
worker:
build: ./worker
depends_on:
web:
condition: service_healthy
The healthcheck command must be lightweight and test the app itself, not its dependencies. A heavy DB check inside a healthcheck turns the container unhealthy at the first network hiccup.
What a healthcheck cannot see
HEALTHCHECK runs inside the host: Docker hits localhost inside the container. It does not verify whether the service is reachable from outside — through a reverse proxy, load balancer, DNS, and public TLS. A container can be healthy while the site is unreachable because of an expired certificate on nginx or broken DNS.
An external layer on top of containers
enterno.io checks the full path from outside, like a user: HTTP response, SSL validity, DNS resolution, and Ping from RU / EU / US regions. This complements the Docker healthcheck: the healthcheck heals the container, the external monitor catches problems at the perimeter. Free tier with 10 monitors at a 5-minute interval (paid — 1 minute / 30 seconds).
For containerized cron jobs and массовую проверку URL workers, use heartbeat monitoring: the container pings a unique URL at the end of the task; a missed ping is an alert that the task did not run. This is more reliable than a healthcheck for one-off jobs that exit.
FAQ
How is HEALTHCHECK different from a Kubernetes readinessProbe?
HEALTHCHECK is a Docker feature. In Kubernetes, probes are managed by kubelet and ignore the Docker HEALTHCHECK. In k8s, use liveness/readiness probes directly.
Why is a container unhealthy even though the app works?
Usually the healthcheck command is wrong: no curl in the image, wrong port or path, too short a timeout. Check docker inspect → the Health section.
Do I need curl in the image for a healthcheck?
Not necessarily: you can use wget, the app's built-in healthcheck (node healthcheck.js), or a TCP check. In alpine, curl is installed separately.
How do I monitor a containerized cron job?
Use heartbeat: the job pings enterno.io/heartbeat at the end; no ping within the window is an alert about a job that did not run or hung.
Add an outside check: create a monitor at enterno.io/heartbeat for cron jobs or at /monitors for the site. Useful reading: container monitoring, health-check endpoints, monitoring for developers.