# Should I define the healthcheck in the Dockerfile or leave it to Kubernetes probes?

> Define readiness, liveness and startup as Kubernetes probes; keep HEALTHCHECK only if the image also runs under Compose, pointing both at one endpoint.

- Asked: 2026-07-19
- Answered: 2026-07-21
- Asked by: Elif
- Tags: docker, kubernetes, guvenilirlik
- Source: https://muhammetsafak.com/just-ask/should-i-define-the-healthcheck-in-the-dockerfile-or-leave-it/
- Language: en-US
- Author: Muhammet Şafak

---
**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?


Short answer: in Kubernetes, probes own readiness/liveness; the kubelet never reads the image's `HEALTHCHECK` status, by design.

## Short answer

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. The split inside the probes themselves — what liveness should measure versus readiness — is something I detailed in [the health check design answer](/just-ask/health-check-design-liveness-vs-readiness/).

## Why

1. **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.
2. **The concepts belong to the Kubernetes side.** Readiness (traffic gating), liveness (restart), and startup (slow boot) are all Kubernetes probe concepts. That's the layer that owns "can this pod serve".
3. **Docker/Compose do honor HEALTHCHECK.** In local dev, Compose's `depends_on: condition: service_healthy` and plain Docker/Swarm use HEALTHCHECK to mark a container "healthy." So it isn't useless; it just serves a different consumer.
4. **HEALTHCHECK has a cost.** `CMD curl` needs `curl` in the image (distroless won't have it). A shell-form HEALTHCHECK also spawns a process on every interval, which is wasted work on a container whose orchestrator never reads the result.

## What to do

1. **Define the probes in the Deployment/Pod spec.** That's the layer that actually gates traffic and restarts; spell out readiness, liveness and startup there.

   ```yaml
   # 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
   ```

2. **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.
3. **Either write the HEALTHCHECK properly or drop it.** Prefer exec-ing your binary's own `healthcheck` subcommand over `CMD curl`; drop HEALTHCHECK entirely if the image only ever runs in k8s.

   ```dockerfile
   # Only meaningful if the image also runs under Compose/plain Docker:
   HEALTHCHECK --interval=10s --timeout=2s --retries=3 \
     CMD ["/app/server", "healthcheck"]
   ```

4. **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.

**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.

## Related Reading

- [My Go worker in a container never receives SIGTERM and won't shut down gracefully—is this a PID 1 problem?](https://muhammetsafak.com/just-ask/go-worker-never-receives-sigterm-pid-1-problem/) — Just Ask
- [My non-root container can't write to the mounted storage directory—how do I fix the permissions?](https://muhammetsafak.com/just-ask/my-non-root-container-cant-write-to-the-mounted-storage-directory/) — Just Ask
- [How do I isolate self-hosted CI/CD runners and build an ephemeral, clean-after-each-run environment?](https://muhammetsafak.com/just-ask/isolating-and-securing-self-hosted-ci-cd-runners/) — Just Ask
