Skip to content
← All articles

Docker Healthcheck Guide

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 retries times 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

ParameterPurposeTypical value
--intervalPeriod between checks30s
--timeoutHow long to wait for a reply5s
--start-periodGrace time for startup20–60s
--retriesConsecutive failures before unhealthy3

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.

Check your website right now

Check your site →
More articles: DevOps
DevOps
Monitoring RAG Pipelines
22.06.2026 · 34 views
DevOps
Uptime Checks in CI/CD Pipelines
18.06.2026 · 36 views
DevOps
Website Monitoring with Grafana: Dashboards and Alerts
18.06.2026 · 34 views
DevOps
Zero-Downtime Deployment Strategies
16.03.2026 · 144 views