I built a small linter for health checks that disagree with rollouts

A declared readiness rollout gate disagrees with a Docker Compose healthcheck that calls only liveness

Original diagram for this Docker-only build note. It shows a configuration disagreement, not a production incident.

I kept tripping over the same sentence while writing about health checks: “we gate the rollout on readiness.”

It sounds reassuring. Then you open the deployment file and find a healthcheck that calls /healthz, because that endpoint was easy to add and it returns 200 as long as the process is alive.

Neither file is necessarily wrong on its own. Together, they can tell two different stories.

So I made a small CLI called probe-contract. It compares a declared health contract with a Docker Compose file and reports the disagreements that are easy to miss in review.

The first release is deliberately small. It does not call a live endpoint. It does not attempt to infer whether a checkout flow, queue, or database is truly healthy. It reads configuration and asks a narrower question: does the probe in the deployment file support the health assumption you wrote down?

The smallest useful contract

The tool takes a tiny YAML file alongside Compose:

services:
  api:
    liveness: /healthz
    readiness: /readyz
    user_path: /checkout
    rollout_gate: readiness

Then it compares that declaration with the Compose healthcheck.

For the mismatch case, the contract said readiness should gate the rollout, while the Compose probe called only /healthz:

WARNING  api: rollout_gate is readiness, but Compose healthcheck does not reference /readyz (READINESS_NOT_PROBED)

That warning is the whole point of the first version. It does not prove /readyz is a good readiness endpoint. It makes the disagreement visible before somebody treats a green container as proof that a rollout is safe.

I tested the tool in a container too

I ran the released CLI against its fixtures inside a Docker container with a read-only filesystem, a non-root user, dropped capabilities, and no-new-privileges.

The valid fixture produced empty JSON:

{
  "diagnostics": []
}

A Compose file with its healthcheck explicitly disabled produced an error and exit code 1:

ERROR  api: service has no Compose healthcheck (HEALTHCHECK_MISSING)

That second case mattered. An early review of the tool found that healthcheck: { disable: true } could look like an active healthcheck to a naive YAML parser. The release now treats both disable: true and Docker’s test: [NONE] form as missing checks.

The review also caught a less obvious false pass: /ready should not match /readyz just because one string contains the other. That is fixed too. A health contract is already an approximation; the checker should not add accidental ambiguity on top.

What it checks today

probe-contract v0.1.0 checks a few things and stops there:

  • a service declared in the contract is present in Compose;
  • that service has an active healthcheck;
  • a readiness rollout gate actually probes the declared readiness path;
  • liveness and readiness are not declared as the same endpoint by accident;
  • interval, timeout, and retries are present on the healthcheck.

It emits human-readable text by default and JSON with --format json, which is enough to start using it in CI without making every warning a release blocker.

What it intentionally does not do

I do not want this to become another linter that promises too much.

It does not inspect Kubernetes manifests yet. It does not send traffic to a live service. It does not infer a business transaction from a URL. And it cannot tell whether a dependency should be part of readiness for a particular application.

Those choices belong to the team running the service. The tool only asks them to make the choice explicit, then checks whether the Compose file agrees.

That scope is small enough to be useful. It is also small enough that a reviewer can understand what a warning means without trusting a black box.

Release and next steps

The project is public under MIT and includes a Linux amd64 binary with a checksum:

The next likely steps are Kubernetes manifest support, SARIF output, and a GitHub Action. I am deliberately not calling those features until the Compose contract is useful enough to earn them.


Test notes: I built and ran probe-contract v0.1.0 only in Docker for this note. The demonstration container used a read-only filesystem, non-root UID 65532, dropped Linux capabilities, no-new-privileges, and read-only mounted fixture files. The results are configuration checks against local fixtures, not a benchmark or a production deployment.

Related work: Delivery systems case study · I made a healthy service page on purpose