01 · principles

Principles

cairn splits in exactly one place: the whole build path is public, and the machines that boot the images are private. The catalog holds the image recipes, the CI components that gate them, and the signing and verification tooling, all published so anyone can build the same images; what stays private is the hosts and their host-specific config. No verification anyone runs depends on trusting those hosts or the registry. The base image is boot-gated: every candidate is sealed and booted under enforcing Secure Boot in a nested-KVM job before its :stable moves; host images build on that earned base and promote on a green build of their own.

Read principles →

catalogrecipes · CI · keyspublic, buildable by anyonepublicprivatepromoteddigesthosts (private)run promoted digestshost-specific config

02 · the image

The image

Each image is composed from Fedora packages with no inherited bootc parent to answer for. A builder stage runs rpm-ostree compose rootfs into /target-rootfs, and the final stage is FROM scratch with a single COPY, so nothing the compose did not place survives into the shipped image. The ZFS and NVIDIA kernel modules are built from source in throwaway builder stages, and only the staged output lands in the final variant.

Read the image →

builderrpm-ostree compose rootfsto /target-rootfsbuilder discardedFROM scratchCOPY --from=builder /target-rootfs/ /no builder tools survive

03 · the pipeline

The pipeline

The pipeline composes the image, signs its modules, and boots the whole thing in a throwaway VM before it promotes anything. A sha-tagged candidate fans into the boot gate, sealed-boot, and :stable moves only after every gate passes. A gate that fails skips promote entirely, so :stable stays frozen on the last good image and no regression ships by default.

Read the pipeline →

candidate:<short-sha>gatessealed-bootboots for realpass:stablepromote moves itgate fails:stable frozenpromote skipped, last good stays

04 · the trust chain

The trust chain

All of the trust in this estate comes back to one set of keys I minted once, offline, with the public halves published at /keys/. They sit in two custody tiers: PK and KEK own the firmware and never leave offline escrow, while three operational keys live as masked, protected CI variables so a build can sign a UKI (the same key also covers the kernel modules) or a PCR policy. None of the certificates signs another; trust flows through the enrollment payloads, where PK authorizes KEK and KEK authorizes db.

Read the trust chain →

offline escrow · never in CIPK · KEKRSA-4096 · own the firmwareauthorize dboperational signing keyslive as CI variables · masked, protectedUKI + kmods · PCR11 · cosign

05 · sealing

Sealing

Sealing goes a step past signing: it answers whether what actually booted is the exact artifact that got signed, and lets disk unlock ride on the signer of the measurement instead of a fragile hash. A db-signed systemd-boot loads a single UKI carrying a signed PCR11 policy over a composefs root mounted verity=require, and because the policy binds to the signer rather than a register value, a LUKS2 slot bound to it keeps unlocking across kernel updates. The whole lane is gated in CI, and the sealed tag is the only artifact that installs to disk: the unsealed tags carry no bootloader at all.

Read sealing →

UKIsigned with db · no shim.pcrsig · .pcrpkey (PCR11)measured to PCR11signed PCR11 policybinds to the signer, not a hashsd-stub supplies .pcrsigLUKS2 unlockcryptenroll --tpm2-public-key-pcrs=11

06 · running it

Running it

Running a host on cairn comes down to forking a base variant, adding the host-specific config, and pushing; CI builds and promotes the fork on its own and never reports back upstream. A host runs only promoted digests, and the one lane with working runtime enforcement, the evidence lane, checks each pull against a baked policy.json and the committed cosign public key before it deploys. Anyone, including the host, can re-verify the same digest against the published fingerprint at /keys/, out of band, so trust comes from the fingerprint and not from access to the repo.

Read running it →

fetchpodman pullbootc switchcontainers/imagepolicy engineone policy, one verdictverdictdeploy, orfail closedcosign.pubbaked, published/keys/verify signature

07 · install

Install

Pick a :stable image or a forked vignette, then run the exact bootc install to-disk invocation, passing --block-setup tpm2-luks explicitly: a LUKS2 root bound to the TPM with no fallback keyslot. First boot rides that single slot, so the walkthrough adds a recovery passphrase before anything else, then picks a posture: bind-tpm's signed-PCR11 rebind (the enrollment sealing is built around), password-only, or skip. Four vignette lanes each land on a different point along this path.

Read install →

choose image:stable / forkbootc installto-disk--block-setup tpm2-luksrecovery passphraseadd before anything elseTPM posturebind-tpm · password-only · skip