03 · the pipeline
The pipeline
Build, sign, boot-gate, promote: the pipeline that composes a from-scratch bootc image and moves the flagship's :stable only after it boots in CI.
The pipeline composes a Fedora bootc image from scratch, signs its kernel
modules, seals it, boots the sealed result under enforcing Secure Boot, and only
then moves :stable. Most of that mechanism lives somewhere other than the
repository that runs it:
base
includes base-build-scratch and seal from the public catalog at
pipeline and writes only what
the catalog leaves blank, the gates that decide whether a candidate may move.
The catalog owns how an image is built, base owns what verified means.
Build and sign
The signing key is readable while the modules are built and absent from everything that ships.
build-base composes the rootfs with no upstream bootc parent, then derives the
kernel version from what it just composed so the module builders compile against
exactly that kernel. Every build point emits OCI media types, because bootc’s
composefs backend re-reads the image config blob at install and at bootc upgrade, and a Docker schema2 image fails that read.
What the layers contain is the image.
One path list decides whether any of this runs. A push touching only
documentation, CI configuration or the scripts harness matches none of it, so
nothing builds, promote finds no candidate, and no cascade fires; an
operator-fired rebuild, a schedule, an API run or a downstream trigger builds
regardless of paths. Ahead of all of it, preflight-alignment stands the run
down when Fedora has published a kernel whose development and debug packages
have not propagated yet, which otherwise surfaces three stages later as an empty
version tag
(justfile).
The gates
Both promotions hang off the same sealed candidate, and every gate is a hard requirement of the tag it guards.
manifest-gate is the cheap half. It reads the candidate’s module manifests out
of the public registry, normalises the file paths, and diffs them against
committed goldens, printing both drift buckets on a mismatch
(manifest-lib.sh).
Needing only curl, jq and python3, it is the one image-aware gate a merge
request gets; the boot gates need a protected runner with nested
virtualisation.
The sealed suite is the estate’s only boot gate, because the sealed image is the
only configuration an estate host runs
(D27).
It installs the sealed candidate, boots it under enforcing Secure Boot with no
shim in the chain, and reads its receipts out of the running guest
(sealed-boot.sh).
What those receipts prove is the sealing chain, and
the keys behind them are the trust chain.
The flagship’s :stable moves only after every gate has passed, and
base:stable only after its own sealed suite. promote re-pulls the
candidate, re-runs cairn-verify on the far side of
the registry round trip, asserts the media types once more, and retags each
tested digest to :latest and :stable, skipping the copy when the digests
already match. A gate that fails skips promote, and the last good image stays where it
is. That contract is the estate’s central rule.
After a promotion
A promotion posts a trigger to each instance and never waits on it, because a
job token can ask for a pipeline and cannot read one back
(D30).
The full TPM2-LUKS install rehearsal runs on its own schedule and is allowed to
fail: it gates no promotion, so it can carry its whole asserting belt without a
red run holding a release
(D17).
What the instances are is running it. Every image
project sets its own registry cleanup policy instead of taking the framework
default, so :latest, :stable and anything tagged keep- are never in scope
(D32).
The catalog
One catalog holds the mechanism; each consumer decides what it includes and what verified means for its own images.
A component is one spec.inputs unit under
templates/,
released on annotated semver tags. Consumers pin the major range and never an
exact tag, so a patch or a minor reaches every consumer with no commit in any of
them and a major arrives as one reviewable change
(CONTRIBUTING.md).
That range resolves at parse time against published Releases only, which is why
a mutable tag stays banned: it would move under a consumer with nothing released
behind it.