Should I define the healthcheck in the Dockerfile or leave it to Kubernetes probes?
Question
My image defines a `HEALTHCHECK`, but Kubernetes seems to ignore it. Meanwhile the Deployment spec has readiness/liveness probes. I can't figure out which layer owns the "am I ready" decision. Should I write the healthcheck in the Dockerfile, leave it to Kubernetes probes, or have both? What exactly is the relationship between them?
Answer
Short answer: in Kubernetes, probes own readiness/liveness; the kubelet never reads the image’s HEALTHCHECK status, by design. But it’s worth keeping the HEALTHCHECK for Compose/plain Docker — it’s not junk, it just serves a different runtime.
The confusion is understandable: two layers both look like they own health, but only one of them is actually consulted in a given runtime. Pin down which runtime the image is really going to run in first, and the answer falls out of that.
- Kubernetes ignores the Dockerfile HEALTHCHECK by design. The kubelet runs its own liveness/readiness/startup probes and never reads the image’s HEALTHCHECK result. So in k8s that line is dead weight — harmless, but not what gates traffic.
- Which layer owns what. Readiness (traffic gating), liveness (restart), and startup (slow boot) are all Kubernetes probe concepts. That’s the layer that owns “can this pod serve”; define them in the Deployment/Pod spec.
- Docker/Compose do honor HEALTHCHECK. In local dev, Compose’s
depends_on: condition: service_healthyand plain Docker/Swarm use HEALTHCHECK to mark a container “healthy.” So it isn’t useless; it just serves a different consumer. - Don’t duplicate the logic, share the endpoint. Expose the same
/healthz(liveness) and/readyz(readiness) HTTP endpoints; point both the Dockerfile HEALTHCHECK and the k8s probes at them. One implementation, two consumers. - HEALTHCHECK has a cost.
CMD curlneedscurlin the image (distroless won’t have it); prefer exec-ing your binary’s ownhealthchecksubcommand, or just drop HEALTHCHECK entirely if the image only ever runs in k8s. A shell-form HEALTHCHECK also spawns a process on every interval, which is wasted work on a container whose orchestrator never reads the result. - Keep probes cheap and correct. Same golden rule: keep liveness dependency-free, and have readiness check dependencies but cache the result for a few seconds. Otherwise the probes DDoS your database.
# Only meaningful if the image also runs under Compose/plain Docker:
HEALTHCHECK --interval=10s --timeout=2s --retries=3 \
CMD ["/app/server", "healthcheck"]
# In Kubernetes, this is the layer that actually gates traffic and restarts:
readinessProbe:
httpGet: { path: /readyz, port: 8080 }
periodSeconds: 5
livenessProbe:
httpGet: { path: /healthz, port: 8080 }
periodSeconds: 10
Bottom line: personally I’d define readiness/liveness/startup as Kubernetes probes — because that’s the layer that decides traffic and restarts. I’d keep a HEALTHCHECK in the Dockerfile only if the same image also runs under Compose/plain Docker, wiring both at the same /healthz + /readyz endpoints. If the image is k8s-only, I’d drop the HEALTHCHECK to avoid the illusion that it does anything.
Comments
Sign in with your GitHub account to join the discussion. Comments are stored in GitHub Discussions.