Dockerfile: Avoid Multiple HEALTHCHECK Instructions

Multiple HEALTHCHECK instructions in one stage are ambiguous because only the last one applies. Keep a single deterministic probe per stage.

Problem

Only the last HEALTHCHECK in a stage is used. Multiple declarations create false confidence and hide the real runtime probe behavior. Action: keep exactly one health check per stage and treat probe changes as functional changes.

Description

Health checks drive restart behavior, rollout gates, and production diagnostics. If engineers believe multiple checks are active, incident triage starts from incorrect assumptions. Action: remove superseded HEALTHCHECK lines immediately during refactors.

This bug appears when a new probe is appended without deleting the old one. The build stays green, but runtime semantics silently change because Docker keeps only the last instruction. Action: add explicit HEALTHCHECK review to pull-request checklists.

Probe quality matters as much as probe count. Weak probes miss real failure, while expensive probes create false negatives and unnecessary restarts. Action: use a lightweight endpoint that reflects service readiness and configure interval, timeout, and retries explicitly.

Use this rollout checklist for stable probes:

  • Keep one HEALTHCHECK per stage and delete obsolete checks.
  • Verify probe command behavior in a container runtime smoke test.
  • Document what the probe proves, not just what command it runs.
  • If overriding a base-image probe, do it explicitly in the Dockerfile.

Treat verification as part of the rule, not optional cleanup. Action: automate a static check, a build check, and a runtime smoke check in the default CI pipeline so regressions are caught before review.

  • Static check: fail when the disallowed pattern appears in Dockerfile or manifest.
  • Build check: run a minimal image build to confirm the secure pattern is valid.
  • Runtime check: start the workload and assert expected behavior with one deterministic probe.

Examples of code

Problematic code

FROM ubuntu:24.04
HEALTHCHECK --interval=30s CMD curl -fsS http://127.0.0.1:8080/ready || exit 1
HEALTHCHECK --interval=30s CMD wget -q -O- http://127.0.0.1:8080/health || exit 1
CMD ["python3", "-m", "http.server", "8080"]

Verified code

FROM ubuntu:24.04
HEALTHCHECK --interval=30s --timeout=3s --retries=3 CMD curl -fsS http://127.0.0.1:8080/health || exit 1
CMD ["python3", "-m", "http.server", "8080"]

Related rules