<?xml version="1.0" encoding="UTF-8"?><rss version="2.0" xmlns:content="http://purl.org/rss/1.0/modules/content/">
  <channel>
    <title>delivery &amp;mdash; heyimusa</title>
    <link>https://heyimusa.blog/tag:delivery</link>
    <description></description>
    <pubDate>Fri, 04 Sep 2026 06:37:52 +0000</pubDate>
    <item>
      <title>I built a small linter for health checks that disagree with rollouts</title>
      <link>https://heyimusa.blog/i-built-a-small-linter-for-health-checks</link>
      <description>&lt;![CDATA[A declared readiness rollout gate disagrees with a Docker Compose healthcheck that calls only liveness&#xA;&#xA;Original diagram for this Docker-only build note. It shows a configuration disagreement, not a production incident.&#xA;&#xA;I kept tripping over the same sentence while writing about health checks: “we gate the rollout on readiness.”&#xA;&#xA;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.&#xA;&#xA;Neither file is necessarily wrong on its own. Together, they can tell two different stories.&#xA;&#xA;!--more--&#xA;&#xA;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.&#xA;&#xA;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?&#xA;&#xA;The smallest useful contract&#xA;&#xA;The tool takes a tiny YAML file alongside Compose:&#xA;&#xA;services:&#xA;  api:&#xA;    liveness: /healthz&#xA;    readiness: /readyz&#xA;    userpath: /checkout&#xA;    rolloutgate: readiness&#xA;&#xA;Then it compares that declaration with the Compose healthcheck.&#xA;&#xA;For the mismatch case, the contract said readiness should gate the rollout, while the Compose probe called only /healthz:&#xA;&#xA;WARNING  api: rolloutgate is readiness, but Compose healthcheck does not reference /readyz (READINESSNOTPROBED)&#xA;&#xA;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.&#xA;&#xA;I tested the tool in a container too&#xA;&#xA;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.&#xA;&#xA;The valid fixture produced empty JSON:&#xA;&#xA;{&#xA;  &#34;diagnostics&#34;: []&#xA;}&#xA;&#xA;A Compose file with its healthcheck explicitly disabled produced an error and exit code 1:&#xA;&#xA;ERROR  api: service has no Compose healthcheck (HEALTHCHECKMISSING)&#xA;&#xA;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.&#xA;&#xA;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.&#xA;&#xA;What it checks today&#xA;&#xA;probe-contract v0.1.0 checks a few things and stops there:&#xA;&#xA;a service declared in the contract is present in Compose;&#xA;that service has an active healthcheck;&#xA;a readiness rollout gate actually probes the declared readiness path;&#xA;liveness and readiness are not declared as the same endpoint by accident;&#xA;interval, timeout, and retries are present on the healthcheck.&#xA;&#xA;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.&#xA;&#xA;What it intentionally does not do&#xA;&#xA;I do not want this to become another linter that promises too much.&#xA;&#xA;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.&#xA;&#xA;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.&#xA;&#xA;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.&#xA;&#xA;Release and next steps&#xA;&#xA;The project is public under MIT and includes a Linux amd64 binary with a checksum:&#xA;&#xA;Source and README&#xA;v0.1.0 release&#xA;&#xA;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.&#xA;&#xA;---&#xA;&#xA;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.&#xA;&#xA;Related work: Delivery systems case study · I made a healthy service page on purpose&#xA;&#xA;!-- taxonomy: #topicplatformengineering #topicgitopsdelivery --&#xA;]]&gt;</description>
      <content:encoded><![CDATA[<p><img src="/img/articles/probe-contract-makes-health-assumptions-visible.svg" alt="A declared readiness rollout gate disagrees with a Docker Compose healthcheck that calls only liveness"></p>

<p><em>Original diagram for this Docker-only build note. It shows a configuration disagreement, not a production incident.</em></p>

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

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

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



<p>So I made a small CLI called <a href="https://github.com/heyimusa/probe-contract"><code>probe-contract</code></a>. It compares a declared health contract with a Docker Compose file and reports the disagreements that are easy to miss in review.</p>

<p>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?</p>

<h2 id="the-smallest-useful-contract">The smallest useful contract</h2>

<p>The tool takes a tiny YAML file alongside Compose:</p>

<pre><code class="language-yaml">services:
  api:
    liveness: /healthz
    readiness: /readyz
    user_path: /checkout
    rollout_gate: readiness
</code></pre>

<p>Then it compares that declaration with the Compose healthcheck.</p>

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

<pre><code class="language-text">WARNING  api: rollout_gate is readiness, but Compose healthcheck does not reference /readyz (READINESS_NOT_PROBED)
</code></pre>

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

<h2 id="i-tested-the-tool-in-a-container-too">I tested the tool in a container too</h2>

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

<p>The valid fixture produced empty JSON:</p>

<pre><code class="language-json">{
  &#34;diagnostics&#34;: []
}
</code></pre>

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

<pre><code class="language-text">ERROR  api: service has no Compose healthcheck (HEALTHCHECK_MISSING)
</code></pre>

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

<p>The review also caught a less obvious false pass: <code>/ready</code> should not match <code>/readyz</code> 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.</p>

<h2 id="what-it-checks-today">What it checks today</h2>

<p><code>probe-contract v0.1.0</code> checks a few things and stops there:</p>
<ul><li>a service declared in the contract is present in Compose;</li>
<li>that service has an active healthcheck;</li>
<li>a readiness rollout gate actually probes the declared readiness path;</li>
<li>liveness and readiness are not declared as the same endpoint by accident;</li>
<li>interval, timeout, and retries are present on the healthcheck.</li></ul>

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

<h2 id="what-it-intentionally-does-not-do">What it intentionally does not do</h2>

<p>I do not want this to become another linter that promises too much.</p>

<p>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.</p>

<p>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.</p>

<p>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.</p>

<h2 id="release-and-next-steps">Release and next steps</h2>

<p>The project is public under MIT and includes a Linux amd64 binary with a checksum:</p>
<ul><li><a href="https://github.com/heyimusa/probe-contract">Source and README</a></li>
<li><a href="https://github.com/heyimusa/probe-contract/releases/tag/v0.1.0">v0.1.0 release</a></li></ul>

<p>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.</p>

<hr>

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

<p><strong>Related work:</strong> <a href="/portfolio/work/#delivery-systems">Delivery systems case study</a> · <a href="/i-made-a-healthy-service-page-on-purpose">I made a healthy service page on purpose</a></p>


]]></content:encoded>
      <guid>https://heyimusa.blog/i-built-a-small-linter-for-health-checks</guid>
      <pubDate>Mon, 20 Jul 2026 13:26:02 +0000</pubDate>
    </item>
    <item>
      <title>I made a healthy service page on purpose</title>
      <link>https://heyimusa.blog/i-made-a-healthy-service-page-on-purpose</link>
      <description>&lt;![CDATA[One service, three health signals: liveness stays green while readiness and the checkout path fail&#xA;&#xA;Original diagram for a Docker-only experiment in this note. It models a failed dependency, not a production system.&#xA;&#xA;I made a service look healthy on purpose.&#xA;&#xA;Not healthy to a user. Healthy to the one check that only cared whether the process was still running.&#xA;&#xA;!--more--&#xA;&#xA;It is an easy trap. A container is up, the liveness endpoint returns 200, the dashboard stays green, and everyone gets to feel better for a few minutes. Meanwhile the dependency the service needs has gone away, requests are timing out, and the thing users came for is unavailable.&#xA;&#xA;I built a tiny version of that failure in a disposable Docker container. The service had three endpoints:&#xA;&#xA;/healthz   process liveness&#xA;/readyz    dependency-aware readiness&#xA;/checkout  a small user-facing path&#xA;&#xA;With the simulated dependency available, all three were fine:&#xA;&#xA;/healthz   http=200 body=process=up&#xA;/readyz    http=200 body=dependency=reachable&#xA;/checkout  http=200 body=checkout=accepted&#xA;&#xA;Then I restarted the same demo with one environment flag that simulated a dependency failure. The Python process did not crash. That is important.&#xA;&#xA;/healthz   http=200 body=process=up&#xA;/readyz    http=503 body=dependency=unavailable&#xA;/checkout  http=503 body=checkout=unavailable: dependency timeout&#xA;&#xA;The liveness check was not lying. It answered the question it was given: is the process alive? The problem is that it was the wrong question for deciding whether to send a user more traffic, or whether a rollout was safe to keep.&#xA;&#xA;Three questions that should not share one endpoint&#xA;&#xA;I find it useful to separate health checks by the decision they support.&#xA;&#xA;/healthz asks whether restarting the process is likely to help. A process that cannot accept a TCP connection, is deadlocked, or has stopped responding belongs here. This check should be narrow. If every temporary dependency wobble makes it fail, an orchestrator can turn a recoverable outage into a restart loop.&#xA;&#xA;/readyz asks whether the instance should receive work. In the experiment, it included the dependency state. That made it a reasonable signal for traffic admission and rollout progress. A failing readiness check can take an instance out of rotation without pretending that the process needs to be killed.&#xA;&#xA;/checkout asks the least convenient question: can someone complete the thing they actually came to do? It is usually more expensive to measure and should not become a noisy probe that hits every dependency every second. But some form of user-path signal belongs in monitoring. Otherwise a green fleet can hide a useless product.&#xA;&#xA;A green container is a weak promise&#xA;&#xA;Container state is still useful. It tells you whether a workload exists and whether the runtime can keep it alive. It does not tell you that the application has a connection pool, that the queue is moving, or that an important request succeeds.&#xA;&#xA;That distinction matters most during a deployment. If a new version starts successfully but cannot talk to its required dependency, a liveness-only check may let the rollout continue. The failure then moves from deployment time to user time, where it is noisier and harder to unwind.&#xA;&#xA;This is also why I do not like treating readiness as a cosmetic endpoint added late in a project. It is part of the contract between the application and the platform. The platform needs an honest answer before it decides to route traffic or declare a revision ready.&#xA;&#xA;Do not turn readiness into a dependency census&#xA;&#xA;There is a bad version of this pattern too: make /readyz call every downstream service, every time, and fail on any brief hiccup.&#xA;&#xA;That can create its own outage. If a shared dependency has a short blip, hundreds of instances may all become unready at once. A probe that was meant to reduce risk becomes a traffic switch with no damping.&#xA;&#xA;The useful questions are smaller:&#xA;&#xA;Which dependency makes this instance incapable of doing its primary job?&#xA;How long must that dependency be unavailable before traffic should stop?&#xA;Can the check use a bounded timeout and cached result instead of adding load during an incident?&#xA;What signal tells us the user path is degraded even if the process remains alive?&#xA;&#xA;The answers will differ by service. A worker may be alive and intentionally disconnected while a queue is paused. A checkout API with no database connection is not in the same situation.&#xA;&#xA;What I would wire into a real rollout&#xA;&#xA;For a non-critical environment, I would start with a deliberately boring drill:&#xA;&#xA;deploy a candidate revision;&#xA;make one required dependency unavailable in a controlled way;&#xA;confirm that readiness fails while liveness stays stable;&#xA;confirm that the traffic or rollout controller reacts to readiness, not just process survival;&#xA;restore the dependency and verify the user-facing path, not only the pod state.&#xA;&#xA;That drill has a nice side effect: it makes the rollback criteria concrete. In my previous note, I argued that a rollout needs a return address. This is part of the address book. If the only evidence after a rollback is “the pods are running,” there is still a lot left to guess.&#xA;&#xA;---&#xA;&#xA;Test notes: I ran this in two disposable Docker containers using Python 3.12. Each container used a read-only root filesystem, dropped Linux capabilities, no-new-privileges, a PID limit of 64, 0.25 CPU, and 128 MiB memory. The dependency failure was a local simulation. No host service, production endpoint, cluster, or external dependency was accessed.&#xA;&#xA;Related reading: A rollout needs a return address · Delivery systems case study&#xA;&#xA;!-- taxonomy: #topicplatformengineering --&#xA;]]&gt;</description>
      <content:encoded><![CDATA[<p><img src="/img/articles/health-signals-are-not-the-same.svg" alt="One service, three health signals: liveness stays green while readiness and the checkout path fail"></p>

<p><em>Original diagram for a Docker-only experiment in this note. It models a failed dependency, not a production system.</em></p>

<p>I made a service look healthy on purpose.</p>

<p>Not healthy to a user. Healthy to the one check that only cared whether the process was still running.</p>



<p>It is an easy trap. A container is up, the liveness endpoint returns <code>200</code>, the dashboard stays green, and everyone gets to feel better for a few minutes. Meanwhile the dependency the service needs has gone away, requests are timing out, and the thing users came for is unavailable.</p>

<p>I built a tiny version of that failure in a disposable Docker container. The service had three endpoints:</p>

<pre><code class="language-text">/healthz   process liveness
/readyz    dependency-aware readiness
/checkout  a small user-facing path
</code></pre>

<p>With the simulated dependency available, all three were fine:</p>

<pre><code class="language-text">/healthz   http=200 body=process=up
/readyz    http=200 body=dependency=reachable
/checkout  http=200 body=checkout=accepted
</code></pre>

<p>Then I restarted the same demo with one environment flag that simulated a dependency failure. The Python process did not crash. That is important.</p>

<pre><code class="language-text">/healthz   http=200 body=process=up
/readyz    http=503 body=dependency=unavailable
/checkout  http=503 body=checkout=unavailable: dependency timeout
</code></pre>

<p>The liveness check was not lying. It answered the question it was given: is the process alive? The problem is that it was the wrong question for deciding whether to send a user more traffic, or whether a rollout was safe to keep.</p>

<h2 id="three-questions-that-should-not-share-one-endpoint">Three questions that should not share one endpoint</h2>

<p>I find it useful to separate health checks by the decision they support.</p>

<p><code>/healthz</code> asks whether restarting the process is likely to help. A process that cannot accept a TCP connection, is deadlocked, or has stopped responding belongs here. This check should be narrow. If every temporary dependency wobble makes it fail, an orchestrator can turn a recoverable outage into a restart loop.</p>

<p><code>/readyz</code> asks whether the instance should receive work. In the experiment, it included the dependency state. That made it a reasonable signal for traffic admission and rollout progress. A failing readiness check can take an instance out of rotation without pretending that the process needs to be killed.</p>

<p><code>/checkout</code> asks the least convenient question: can someone complete the thing they actually came to do? It is usually more expensive to measure and should not become a noisy probe that hits every dependency every second. But some form of user-path signal belongs in monitoring. Otherwise a green fleet can hide a useless product.</p>

<h2 id="a-green-container-is-a-weak-promise">A green container is a weak promise</h2>

<p>Container state is still useful. It tells you whether a workload exists and whether the runtime can keep it alive. It does not tell you that the application has a connection pool, that the queue is moving, or that an important request succeeds.</p>

<p>That distinction matters most during a deployment. If a new version starts successfully but cannot talk to its required dependency, a liveness-only check may let the rollout continue. The failure then moves from deployment time to user time, where it is noisier and harder to unwind.</p>

<p>This is also why I do not like treating readiness as a cosmetic endpoint added late in a project. It is part of the contract between the application and the platform. The platform needs an honest answer before it decides to route traffic or declare a revision ready.</p>

<h2 id="do-not-turn-readiness-into-a-dependency-census">Do not turn readiness into a dependency census</h2>

<p>There is a bad version of this pattern too: make <code>/readyz</code> call every downstream service, every time, and fail on any brief hiccup.</p>

<p>That can create its own outage. If a shared dependency has a short blip, hundreds of instances may all become unready at once. A probe that was meant to reduce risk becomes a traffic switch with no damping.</p>

<p>The useful questions are smaller:</p>
<ul><li>Which dependency makes this instance incapable of doing its primary job?</li>
<li>How long must that dependency be unavailable before traffic should stop?</li>
<li>Can the check use a bounded timeout and cached result instead of adding load during an incident?</li>
<li>What signal tells us the user path is degraded even if the process remains alive?</li></ul>

<p>The answers will differ by service. A worker may be alive and intentionally disconnected while a queue is paused. A checkout API with no database connection is not in the same situation.</p>

<h2 id="what-i-would-wire-into-a-real-rollout">What I would wire into a real rollout</h2>

<p>For a non-critical environment, I would start with a deliberately boring drill:</p>
<ol><li>deploy a candidate revision;</li>
<li>make one required dependency unavailable in a controlled way;</li>
<li>confirm that readiness fails while liveness stays stable;</li>
<li>confirm that the traffic or rollout controller reacts to readiness, not just process survival;</li>
<li>restore the dependency and verify the user-facing path, not only the pod state.</li></ol>

<p>That drill has a nice side effect: it makes the rollback criteria concrete. In my previous note, I argued that a rollout needs a return address. This is part of the address book. If the only evidence after a rollback is “the pods are running,” there is still a lot left to guess.</p>

<hr>

<p><strong>Test notes:</strong> I ran this in two disposable Docker containers using Python 3.12. Each container used a read-only root filesystem, dropped Linux capabilities, <code>no-new-privileges</code>, a PID limit of 64, 0.25 CPU, and 128 MiB memory. The dependency failure was a local simulation. No host service, production endpoint, cluster, or external dependency was accessed.</p>

<p><strong>Related reading:</strong> <a href="/a-rollout-needs-a-return-address">A rollout needs a return address</a> · <a href="/portfolio/work/#delivery-systems">Delivery systems case study</a></p>


]]></content:encoded>
      <guid>https://heyimusa.blog/i-made-a-healthy-service-page-on-purpose</guid>
      <pubDate>Mon, 20 Jul 2026 10:12:13 +0000</pubDate>
    </item>
  </channel>
</rss>