Does the Dockerfile `HEALTHCHECK` instruction still do anything once a scheduler runs your image?
answer
- The instruction configures the engine, not the app
- A state is recorded; nothing acts on it
- docker ps STATUS and .State.Health
- The schedule leaves, the check command stays
- No shell in scratch means exec form only
basics
~20 sMostly no. The engine runs HEALTHCHECK and records healthy/unhealthy, but takes no action on it; a scheduler declares its own probes in its spec and acts on those. Kubernetes ignores the image's HEALTHCHECK entirely. Keep the check command in the image — the probe definition moves out.
solid answer
~50 s`HEALTHCHECK` tells the *engine* to run a command inside the container on an interval and record a state — `starting`, then `healthy` or `unhealthy` after `--retries` consecutive failures. Under plain `docker run` that state is only reported: it shows in `docker ps`, in `docker inspect --format '{{.State.Health.Status}}'`, and as a `health_status` event. The engine does not restart or replace anything because of it. A scheduler brings its own probes, defined in its workload spec with its own intervals and actions, and generally does not read the image's instruction at all — Kubernetes, for example, ignores it. What is still worth keeping in the image is the *check itself*: a cheap endpoint or a `healthcheck` subcommand in your binary, so `docker run` and Compose locally get the same signal the platform will ask for. Use `HEALTHCHECK NONE` to drop one inherited from a base image.
code
dockerfile · 7 linesFROM scratch
COPY --chmod=0555 checkout /checkout
USER 10001:10001
EXPOSE 8143
HEALTHCHECK --interval=20s --timeout=2s --start-period=5s --retries=3 \
CMD ["/checkout", "healthcheck"]
ENTRYPOINT ["/checkout"]go deeper
Recall the instruction's shape and its options: --interval, --timeout, --start-period, --retries and the CMD it runs. Know that the result shows up in the STATUS column of docker ps as healthy or unhealthy.
Explain the mechanics: the daemon runs the command inside the container, counts consecutive failures, and records a state that nothing acts on by itself. Be able to say where a scheduler's probes come from instead and why the schedule is runtime configuration.
Demonstrate the operational judgement — shallow versus deep checks, why probing dependencies amplifies incidents, how you debug a flapping probe from .State.Health, and how you keep the local and production checks the same implementation.
Own the fleet-wide convention: one check implementation shipped in every image, thresholds owned by the platform spec, and a clear split between a signal that removes traffic and a signal that destroys an instance.
## What the instruction actually does `HEALTHCHECK` is an image-level instruction that configures the **Docker engine**, not the application. It looks like this: ``` HEALTHCHECK --interval=30s --timeout=3s --start-period=10s --retries=3 \ CMD ["/checkout", "healthcheck"] ``` When a container from that image runs, the daemon executes the command **inside the container** every `--interval`, giving it `--timeout` to finish. Exit status 0 means healthy; 1 means unhealthy. Failures during `--start-period` do not count against the container, which is how you tolerate a slow start without slackening the steady-state interval. After `--retries` consecutive failures the container's health state flips to `unhealthy`. The state is observable in three places: the `STATUS` column of `docker ps` (`Up 4 minutes (healthy)`), `docker inspect --format '{{json .State.Health}}'` — which also gives you `FailingStreak` and the last few probe outputs with their exit codes, invaluable when the check itself is broken — and the event stream, where the daemon emits `health_status: unhealthy`. **And then nothing happens.** This is the part candidates get wrong. Under plain `docker run` the engine records health and acts on it in no way at all: it will not restart the container, will not stop it, and cannot stop other containers reaching it, because the restart policy reacts to the container *exiting*, not to it being unhealthy. A container can sit `unhealthy` for a week serving errors. The instruction produces a signal for something else to consume. ## What changes at the handoff A scheduler is that something else — but it brings its own. Probes are declared in the workload spec, with the platform's own semantics for interval, timeout, failure threshold and, crucially, **action**: restart this instance, replace it, or take it out of the load-balancing set. Two consequences follow. First, the probe definition moves out of the image. Intervals and thresholds are operational settings that differ per environment — a staging deployment can afford an aggressive probe that would cause flapping in production — so they belong with the other runtime configuration, next to the resource limits and the restart behaviour. Second, the image's instruction may simply not be read. Kubernetes does not look at `HEALTHCHECK`; its probes come entirely from the pod spec. Other systems differ, and Docker's own Compose file has a `healthcheck:` key that can override or disable what the image declares. So an image whose only health story is a `HEALTHCHECK` line will be probed by nothing at all on some platforms, which is exactly the failure mode where a broken instance quietly keeps receiving traffic. ## What is still worth keeping in the image The **check command** — as opposed to the schedule around it. Whatever runs your container, something wants a cheap, dependency-light way to ask "are you serving?". Shipping that as part of the artefact means `docker run` on a laptop, a Compose file in CI, and the production spec all ask the same question the same way. That has a sharp edge on minimal images. An order-checkout API that ships as a single Go binary in a `scratch` image has no shell and no `curl`, so the familiar ``` HEALTHCHECK CMD curl -f http://localhost:8143/healthz || exit 1 ``` cannot work: shell form wraps the command in `/bin/sh -c`, and there is no `/bin/sh`. The fix is to make the binary check itself and use exec form: ``` HEALTHCHECK --interval=20s --timeout=2s --start-period=5s --retries=3 \ CMD ["/checkout", "healthcheck"] ``` The subcommand dials `127.0.0.1:8143/healthz` in-process and exits 0 or 1. That binary is then equally usable as a scheduler's exec-style probe, so one implementation serves both worlds. ## Design rules for the check itself - **Keep it shallow.** A liveness-style check should prove the process is serving, not that every dependency is up. If the check touches the database, a database blip becomes an event that kills or replaces every instance simultaneously — you converted a partial outage into a total one. - **Keep it cheap.** It runs forever, on every instance, at whatever interval the platform picks. - **Do not authenticate it.** The probe usually cannot carry credentials; expose the endpoint on a path that requires none and returns no sensitive detail. - **Distinguish "started" from "ready".** `--start-period` handles slow starts under the engine; a scheduler will usually want a separate readiness signal so it can withhold traffic without killing the instance. - **Use `HEALTHCHECK NONE`** when a base image ships a check that is wrong for your process — inheritance is otherwise silent.
- How do you find out why a container is stuck reporting `unhealthy`?`docker inspect --format '{{json .State.Health}}'` returns `Status`, `FailingStreak` and a `Log` array holding the last few probe runs with their start time, exit code and captured output. That output is usually the answer: a missing binary, a wrong port, or a timeout that the check exceeded. `docker ps` only shows the summarised state.
- Should the check verify the database connection too?Not for a liveness-style check. If every instance probes the database, one database blip marks every instance unhealthy at once and the platform replaces the whole fleet, turning a recoverable dependency problem into an outage. Keep the check inside the process, and let dependency health surface through metrics and readiness signals that withhold traffic instead of killing instances.
- What does `HEALTHCHECK NONE` do?It disables any healthcheck inherited from the base image, so containers from your image have no health state at all. It is the right move when a base image ships a check tuned for a different process — otherwise the inherited command runs silently in your container and can report your service unhealthy for reasons that have nothing to do with it.
saying these in an interview costs you the question
- Thinks the engine restarts an unhealthy container
- Assumes every scheduler reads the image's HEALTHCHECK
- Writes a shell-form healthcheck for a scratch image
- Probes the database from a liveness check
- Bakes production intervals and thresholds into the image
- Confuses the health state with the restart policy