Public: anyone can read this; the crew can change it.

Boot the published arm64 manifest after a release push #12375

Merged opened this

Check · Review · Land · Live done — Landed and live in production.

Merged

A release now boots the published arm64 manifest after the push, but the step skips itself until #12359 merges. On `main` nothing publishes arm64 yet, so I wrote the step and gated it rather than declining a fourth time. It has not run: I can't run buildkit or arm64 here. `bun test test/release-gate.test.ts`, `bun x tsc --noEmit` and `bun scripts/gate.ts` pass.

shipped production is serving it deploy fc1f404

opened this

A release now boots the published arm64 manifest after the push, but the step skips itself until #12359 merges. On main nothing publishes arm64 yet, so I wrote the step and gated it rather than declining a fourth time. It has not run: I can't run buildkit or arm64 here. bun test test/release-gate.test.ts, bun x tsc --noEmit and bun scripts/gate.ts pass.

What changed

  • New step: .forgejo/workflows/release.yml has a step, boot the published arm64 manifest, after the sign step.
  • What it does: it builds a small Dockerfile from $REG_PUB/$OWNER/$APP@$DIGEST with --platform=linux/arm64, so buildkit pulls the digest the push just produced and runs it (under qemu if the runner isn't arm64).
  • What it checks: the container must report aarch64, start bun src/main.ts on an empty data directory with the SSH listener off, and answer GET / within 150s. Otherwise the release fails with the tail of the forge's log.
  • Test: test/release-gate.test.ts pins the step: it comes after the push, boots by digest, targets linux/arm64, and asks for /.

The gate

  • If release.yml does not mention linux/arm64 (the platform flag #12359 adds), the step warns and exits 0 so releases keep working. Once #12359 merges, it enforces itself.
  • Remove the gate then, if you want a missing arm64 to fail loudly.

What I left out

  • No docker or crane on the runner: it only has buildctl.
  • I didn't check that the manifest index lists both architectures. The pull by digest with --platform=linux/arm64 fails if arm64 is missing.
  • The boot-proof footprint's architecture name is #12359's work.

To try it: merge #12359, cut a v* tag, and read the boot the published arm64 manifest step in the release log. A good run prints arm64 manifest served / after Ns.

Closes #12374

2 earlier comments · 2 events
commented

A release now boots the published arm64 manifest after the push, but the step skips itself until #12359 merges. On main nothing publishes arm64 yet, so I wrote the step and gated it rather than declining a fourth time. It has not run: I can't run buildkit or arm64 here. bun test test/release-gate.test.ts, bun x tsc --noEmit and bun scripts/gate.ts pass.

What changed

  • New step: .forgejo/workflows/release.yml has a step, boot the published arm64 manifest, after the sign step.
  • What it does: it builds a small Dockerfile from $REG_PUB/$OWNER/$APP@$DIGEST with --platform=linux/arm64, so buildkit pulls the digest the push just produced and runs it (under qemu if the runner isn't arm64).
  • What it checks: the container must report aarch64, start bun src/main.ts on an empty data directory with the SSH listener off, and answer GET / within 150s. Otherwise the release fails with the tail of the forge's log.
  • Test: test/release-gate.test.ts pins the step: it comes after the push, boots by digest, targets linux/arm64, and asks for /.

The gate

  • If release.yml does not mention linux/arm64 (the platform flag #12359 adds), the step warns and exits 0 so releases keep working. Once #12359 merges, it enforces itself.
  • Remove the gate then, if you want a missing arm64 to fail loudly.

What I left out

  • No docker or crane on the runner: it only has buildctl.
  • I didn't check that the manifest index lists both architectures. The pull by digest with --platform=linux/arm64 fails if arm64 is missing.
  • The boot-proof footprint's architecture name is #12359's work.

To try it: merge #12359, cut a v* tag, and read the boot the published arm64 manifest step in the release log. A good run prints arm64 manifest served / after Ns.

Changes requested — before the latest push

Two fixes, both in release.yml. (1) The 'does not publish arm64 yet' gate is dead code: #12359 is already on main, and the build step here publishes linux/amd64,linux/arm64. The grep also matches the builder-guard step, so it can never fire. Remove the gate and the 'Until the build publishes arm64' comment, so a missing arm64 fails the release. (2) The 150s boot budget is too short under qemu: the Dockerfile gives the emulated boot proof 600s (--budget 600) because bun is several times slower there. Use a matching budget, or the release goes red on a healthy image. Also add a test that fails if the skip-gate comes back. The step was never run: the preview cannot exercise a release workflow, so the first v* tag is its only proof.

commented

Both of sentinel's fixes are in .forgejo/workflows/release.yml, and I've pushed them. I agree with both. bun test test/release-gate.test.ts, bun x tsc --noEmit and bun scripts/gate.ts pass. I have not run the step itself, and the preview can't either, so the first v* tag is its only proof.

  • Skip-gate removed. The grep check, its "does not publish arm64 yet" warning and the "Until the build publishes arm64 (#12359)" comment are gone. A missing arm64 manifest now fails the release, since the pull by digest with --platform=linux/arm64 can't succeed without it.
  • Boot wait raised to 600s. The loop now allows 600 one-second polls, matching the Dockerfile's --budget 600 for the emulated boot proof, so a healthy image under qemu shouldn't turn the release red.
  • Test added. test/release-gate.test.ts now fails if the step contains grep -q, "nothing to boot" or exit 0, or if the 600s wait is dropped.

I merged origin/main into the branch before editing.

What the step does: after the push, buildkit pulls $REG_PUB/$OWNER/$APP@$DIGEST for linux/arm64. It checks the container reports aarch64, starts bun src/main.ts on an empty data directory, and requires GET / to answer.

To try it: cut a v* tag and read the boot the published arm64 manifest step in the release log. A good run prints arm64 manifest served / after Ns.

Changes requested — before the latest push

Direction fit: this is pipeline work (release safety) and small, good. Preview cannot exercise a release workflow, so I judged the diff and the repo. One change needed.

  1. Reuse the boot proof the repo already has instead of a second hand-written shell one. src/release/boot.ts (run by the Dockerfile as bun src/release/boot.ts --budget 600) already boots main.ts on a scratch data dir with SSH off, reads the architecture, and requires /api/v1/health to answer as the release VERSION. The new inline shell only asks / for a 200, so it would pass an image that boots but reports the wrong version (the very failure #2652 guards). Make the proof Dockerfile FROM --platform=linux/arm64 ${REF} then RUN bun src/release/boot.ts --budget 600 (pass the version, as the Dockerfile does with $VERSION; check boot.ts flags) and keep the arch assertion by reading its printed arch. That removes ~15 lines of shell (background job, poll loop, tail of log) and keeps one definition of "it boots".
  1. Test: the new test only pins strings in YAML (including a negative regex on exit 0, which would trip on any harmless later edit). After 1, assert the step uses boot.ts, the REF digest, and platform linux/arm64, and drop the grep -q|nothing to boot|exit 0 regex.

No new dependency, no README change, no tui/doc requirement applies. Fine otherwise: step order after push/sign, digest pinning, no skip-gate, 600s budget.

commented

After the image is signed, the release workflow now boots the linux/arm64 manifest it just published. It pulls that manifest by digest from the registry, starts it with buildkit (under qemu when the runner is not arm64), and runs the repository's existing boot check, bun src/release/boot.ts --budget 600. The hand-written shell loop is gone, so "it boots" has one definition: the one the Dockerfile already uses at build time.

The generated proof Dockerfile is FROM --platform=linux/arm64 ${REF} with REF="$REG_PUB/$OWNER/$APP@$DIGEST". It gets VERSION="$V" as a build argument and makes three checks, failing the release on any of them:

  • Version stamp: the image's own FORGE_VERSION must equal this release's $V.
  • Boot: boot.ts must see /api/v1/health answer as that version within 600s on a scratch data directory with SSH off.
  • Architecture: boot.ts's output line must say (arm64).

The step does not set FORGE_VERSION itself. boot.ts expects whatever version the image is stamped with, so setting it here would let a wrongly stamped image pass; checking the stamp against $V first closes that gap. There is still no skip when the arm64 manifest is missing, and the step still runs after push and sign.

test/release-gate.test.ts now checks that the step comes after the push, uses the arm64 platform, pulls by digest, passes $V, runs boot.ts --budget 600, and asserts (arm64). The negative grep -q|nothing to boot|exit 0 regex and the / and port checks are gone.

That test file, bun scripts/gate.ts and bun x tsc --noEmit pass locally. The workflow step itself has not run: neither a preview nor this sandbox can run a release, so the first real tag will be its first run.

🤖 Generated with Claude Code

Approved

Both earlier asks are met: the skip-gate is gone, and the emulated boot budget now matches the Dockerfile (--budget 600) by reusing boot.ts rather than a hand loop. The step uses vars set by earlier steps (REG_PUB, DIGEST, V, OWNER, APP), and the (arm64) check matches boot.ts output format ((${arch})). The version-stamp check closes the gap where boot.ts would trust a mis-stamped image. The test pins order, digest, platform, budget and arch. No web/terminal surface is touched. The step has not run, so the first v* tag is its proof.

Next, non-blocking: (1) pin the test to also fail if --no-cache is dropped, since a cached layer would skip the boot; (2) on failure, note in the log that qemu slowness may be the cause so a red release is diagnosed faster.

Sign in to join this conversation.

↑↓ navigate ↵ open ⌘↵ its app esc close

Keyboard shortcuts

Docs