Skip to content

Stamp the container image so a rollout can be read back - #432

Merged
TennyZhuang merged 1 commit into
mainfrom
feat/container-build-stamp
Aug 9, 2026
Merged

Stamp the container image so a rollout can be read back#432
TennyZhuang merged 1 commit into
mainfrom
feat/container-build-stamp

Conversation

@TennyZhuang

Copy link
Copy Markdown
Contributor

Implements the instrument for task #97's container-side readback criterion.

The gap

The deploy passes --containers-rollout and prints the value in the job summary.
There is no container probe anywhere in the pipeline, so "the workflow went green"
has been the only evidence that a rebuilt image is serving — which is precisely
what the container readback criterion rules out. The criterion existed; the
instrument did not.

Shape

file change
container/Dockerfile ARG GIT_SHAENV BUILD_SHA
container/src/server.ts /health reports build
worker/scripts/render-production-config.mjs sets containers[].image_vars.GIT_SHA from GITHUB_SHA
worker/src/index.ts GET /api/admin/container/build reads it back
.github/workflows/deploy-hands-server.yml passes GITHUB_SHA explicitly

Why it is self-controlling

The image running in production contains no code that emits a build key.

key present  => the image was rebuilt and is serving   (the old image cannot fake it)
key absent   => the old image is still serving         (never "the probe missed")

So the next rollout is its own before/after control — no extra deploy is needed
to establish a baseline, and no deploy is being requested by this PR.

Decisions worth reviewing

Build-time, not runtime. A runtime value could only answer "some build made after
the instrument landed"; a build-time value answers which build. image_vars is
wrangler's build-arg channel — its own types say "available to the image at build-time
only"
— and it exists in 4.105.0, the version the lockfile resolves and the deploy
uses. (Checked against 4.105.0 specifically: an unreferenced 4.118.0 also sits in the
pnpm store, and reading types from that one would have proven nothing about the deploy.)

ARG placement is load-bearing. GIT_SHA changes every deploy and an ARG
invalidates every layer below it. Declared beside the other ARGs it would rebuild the
Rust minidump-stackwalk, the apt layer, the Android SDK and npm ci on every deploy.
Placed after COPY src it invalidates two trivial lines, and COPY src already changes
whenever the source does.

No placeholder when unstamped. With GITHUB_SHA unset the renderer omits
image_vars entirely. A stamp that always carries some value cannot separate "built by
a deploy" from "built by hand".

Readback is admin-gated. The exact deployed commit narrows a public repository to
one revision for anyone asking which known issues apply, and this is run by operators.
It returns build: null with stamped: false rather than omitting the key, so a failed
read cannot be misread as an unstamped image.

Verification

  • worker tsc --noEmit: 0 errors, against a 0-error baseline on clean main
    (the checked-out worker-configuration.d.ts was stale and failed identically on main;
    regenerated before comparing, so this is a like-for-like result)
  • container npm run build (tsc --noEmit): 0 errors, baseline 0
  • workflow lint: self-test 4/4, real files exit 0; environment: still bound
  • renderer rendered both ways — the only diff is the image_vars block, and the
    no-SHA render contains 0 occurrences of image_vars/GIT_SHA

Not verified: the image build. No Docker on the machine I work from, so the
Dockerfile change is unbuilt. The two added lines are trivial syntax, but that is an
argument, not a test — worth a reviewer's eye.

Rollout

Rides the next natural container rollout. This PR requests no deploy.

The deploy pipeline has no container probe. It passes --containers-rollout and
prints the value in the job summary, so "the workflow went green" has been the
only evidence that a rebuilt image is serving - which is exactly what the
container-side readback criterion rules out. The criterion existed; the
instrument did not.

  container/Dockerfile     ARG GIT_SHA -> ENV BUILD_SHA
  container/src/server.ts  /health reports `build`
  render-production-config.mjs  sets containers[].image_vars.GIT_SHA from GITHUB_SHA
  worker/src/index.ts      GET /api/admin/container/build reads it back

The stamp is self-controlling, which is the point of it. The image running in
production has no code that emits a `build` key at all, so the key appearing
proves the image was rebuilt and is serving, and the key being absent means the
old image is still up - it can never mean "the probe did not reach the
container". The next rollout is therefore its own before/after control, with no
extra deploy needed to establish a baseline.

Baked in at build time rather than injected at runtime: a runtime value could
only answer "this is some build made after the instrument landed", while a
build-time value answers which build. `image_vars` is wrangler's build-arg
channel - its own types say "available to the image at build-time only" - and
it is present in 4.105.0, the version the lockfile resolves and the deploy uses.

The ARG sits after every expensive layer, deliberately. GIT_SHA changes on every
deploy and an ARG invalidates everything below it; declared beside the other
ARGs it would rebuild the Rust minidump-stackwalk, the apt layer, the Android
SDK and `npm ci` on every deploy. Placed after `COPY src` it invalidates two
trivial lines, and `COPY src` above it already changes whenever the source does.

No stamp is written when GITHUB_SHA is unset, rather than falling back to a
placeholder: a stamp that always carries some value cannot separate "built by a
deploy" from "built by hand", and separating those is the whole purpose.

The readback route is admin-gated. The exact deployed commit narrows a public
repository to one revision for anyone asking which known issues apply, and this
is run by operators, not clients. It returns `build: null` plus `stamped: false`
rather than omitting the key, so a failed read cannot be mistaken for an
unstamped image.

Not verified here: the image build itself. There is no Docker on this machine,
so the Dockerfile change is unbuilt - the two added lines are trivial syntax,
but that is an argument, not a test.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
@TennyZhuang
TennyZhuang force-pushed the feat/container-build-stamp branch from 03f8406 to 7719ea4 Compare August 9, 2026 10:43
@TennyZhuang
TennyZhuang merged commit a1c32f8 into main Aug 9, 2026
7 checks passed
@TennyZhuang
TennyZhuang deleted the feat/container-build-stamp branch August 9, 2026 10:46
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant