Skip to content

Releasing

Updated

Terminal window
tools/release.sh <tag> --stage # build the candidate and bake its image
tools/release.sh <tag> --promote [--no-receipt] # copy the accepted image to stable

The tag is v plus the version in Cargo.toml.

Nothing is built on the workstation. --stage pushes HEAD to the build machine, which builds the artifacts inside containers, bakes the bootc host image from them and pushes it to the release-candidate channel; the script then reads the digest back from the registry. --promote copies that same image, by digest, into the stable channel. The script exists so that everything which has to be true before that copy can be repeated identically instead of reconstructed from memory.

--stage refuses to run unless the working tree is clean, Cargo.toml already carries the version the tag names, and CHANGELOG.md carries that tag’s <!-- release: <tag> --> boundary. It then checks that the build machine’s tree is at the same commit, so the artifacts can only come from the commit being named.

Flag Effect
--stage Push the tree, then build and package it on the build machine, bake the host image and push it to the release-candidate channel. This is the build that acceptance is then run against, and the registry digest it reports is the one the receipt must name
--promote Require an acceptance receipt for the tag whose image.digest equals the digest the release-candidate channel holds, then copy that image, by digest, to the stable channel. Nothing is built
--no-receipt Skip the receipt check. The copy still happens, by digest, from the release-candidate channel; only the owner decides when skipping acceptance is right
  1. Preflight passes. tools/preflight.sh is the local gate for source, licences, advisories, contracts and the whole Rust test suite.
  2. The candidate is staged. The build machine produces the tarball, the packaged ELF, the independent initramfs and the SBOM, bakes the host image from them, and pushes it to the release-candidate channel. Those four artifacts are the set the receipt binds by SHA-256, and the registry digest read back afterwards is what everything downstream is anchored to.
  3. The SBOM describes the shipped bytes. It is generated from the final binary, which carries an auditable dependency record compiled in, rather than from the source tree that was intended to produce it.
  4. The image is baked and accepted. See the bootc host image and the next section. Acceptance runs against the image, so the image has to exist before there is anything to accept.
  5. The receipt matches the tree. docs/releases/acceptance/<tag>.json must record a pass for every required result, and its source digest must still match the tree being released. Its image.digest must also be the digest the release-candidate channel holds for that tag, which is what --promote checks before it copies anything.

docs/RELEASE-ACCEPTANCE.md is the procedure. A candidate is baked to a release-candidate repository, booted on a real Fedora host, and driven through a matrix of destructive and recovery scenarios: lifecycle across an agent restart, snapshot and restore, backup and restore, network realization, AppVM admission and update, escape and isolation checks.

Each result is captured as evidence, and the evidence is checked rather than trusted. tools/check-release-artifact-set.sh re-hashes the tarball, the ELF inside it, the initramfs and the SBOM against the receipt, and tools/check-release-evidence-bundle.sh opens the evidence archive and requires every file the receipt references to be present and to hash as recorded.

Only then is the same image promoted, by digest, to the stable repository. Promotion is a copy, not a second build. Re-baking from the same source tree is not allowed, and since bakes are not byte-reproducible, a re-bake would be visibly a different image.

Earlier versions of this project specified keyless signing bound to a hosted build service’s identity. That was withdrawn: a release is not built on that service, so the identity the signature would attest belongs to something other than the build, and a signature from a different identity is worse than none. A verifier that checks it per the documentation fails, and that failure looks exactly like tampering.

What stands in its place is a chain of five links:

Link What it proves
Tag Which source tree
Receipt That tree passed acceptance, and on which bytes
SHA256SUMS That the release directory did not corrupt before it was baked
host-image-provenance.json Which base digest, kernel and component versions went into the image
Registry digest The exact image a host booted

The last one is the anchor. A tag can be re-pointed; a digest cannot, and it is what bootc status reports on the machine.

A release tag is SemVer, and the version is read out of the artifact filename by the bake. Two rules are enforced rather than remembered: a version containing + cannot become an image tag at all, and a prerelease such as 1.0.1-rc.2 must survive tag derivation untouched, because collapsing every release candidate onto one tag would make the candidate channel useless.