Status: considered and DECLINED by the maintainer, 2026-07-28. Recorded so it is not re-raised without new information. See "Decision" immediately below before reading the rest; the analysis is kept because the reasoning and the one carve-out still matter.
Not adopting any tier. Rollback is the accepted mitigation.
Maintainer's reasoning: this is an image-based OS, the previous deployment is retained, and a bad image can simply be rolled back. That reasoning is sound for every routine failure this would have caught -- image will not boot, module will not load, userspace and kmod versions disagree. All of those fail loudly and reverse cleanly. Building a QEMU boot harness to catch failures that rollback already handles would be effort aimed at the wrong risk.
The one carve-out, and why it is already covered. Rollback does not save you when a newer OpenZFS line (2.4 -> 2.5, not 2.4.3 -> 2.4.4) activates new on-disk pool feature flags: the older image can then no longer import the pool, so you roll back into a system that boots perfectly and cannot read your data. Patch bumps inside a line do not carry this risk.
That case is already gated: renovate.json sets automerge: false for openzfs/zfs
specifically because "once those are activated on a pool the previous image can no longer
import it -- which would break this repo's rollback-to-previous-image recovery path." A line
bump can only arrive as a pull request someone deliberately merges.
What follows from relying on rollback:
- treat an OpenZFS line bump PR differently from every other Renovate PR -- it is the one where "I will just roll back" stops being true
- rollback is load-bearing now, so confirm
bootc rollbackactually works on the target machine once, rather than discovering otherwise during an incident
Reopen this only if the repo starts publishing unattended to machines whose owners cannot roll back, or if ZFS line changes stop being a deliberate, reviewed decision.
The original analysis follows.
This was the last item from the 2026-07-28 production-readiness review, and the one that would gate the claim "safe for unattended promotion to a machine with pools attached."
Promotion to :latest today requires: input resolution, a verified akmods cache (exact kernel,
exact OpenZFS patch, valid signature), a successful image composition, bootc container lint,
a signed candidate, and signature re-verification before the digest is copied.
None of that is runtime evidence. Specifically, nothing has ever proven, before promotion, that:
- the module loads into a running kernel (
modprobe zfssucceeds) - userspace and kmod versions agree at runtime
- a pool can be imported, written to, scrubbed, and exported
- the image boots at all
What is already checked, and should not be re-litigated:
containerfiles/zfs-akmods/install_zfs_from_akmods_cache.py::verify_zfs_module_presentfails the build if nozfs.ko*exists under/lib/modules/<primary-kernel>/extra/zfs, then runsdepmod -a <kernel>for that release- the installer refuses a cache containing two
kmod-zfsRPMs for one kernel bootc container lintruns in theContainerfile
So "the module file exists and is for the right kernel" is covered. "The module works" is not.
The single boot test performed to date (2026-07-28, maintainer, in a VM) was a genuine
end-to-end confirmation — label zfs-version=2.4.3 matched a booted zfs-2.4.3-1 on kernel
7.0.12-201.fc44 — but it was manual, one-off, on a digest that has since been superseded
twice, and had no pool attached.
Partially superseded 2026-07-29: the maintainer's production host switched onto this
image with real multi-terabyte pools already imported, and booted without issue. That confirms
the image boots and the existing import survives a switch -- narrower than what this proposal
calls Tier 3, which is import, write, scrub, export, and rollback compatibility, all confirmed.
Of those, only rollback has since been separately verified (in a test VM; see
"Live Pool State" in safety-model.md). Write/scrub/export on the
production pools specifically have not been exercised under this image. It arrived by the
accepted route regardless -- rollback-backed adoption on real hardware rather than
pre-publication CI testing -- and pools are deliberately not upgraded, which is what keeps that
rollback path available if a problem surfaces later.
These are independent; adopting tier 1 does not require tier 3.
The problem is framed as "unattended promotion." The cheapest correct fix is not to automate a boot test, it is to stop promotion being unattended.
A GitHub Environment supports required reviewers. Putting promote-stable behind such an
environment means :latest cannot move until a human approves the run — at which point the
human can boot the candidate in a VM if they judge it warranted.
-
Cost: near zero. It composes with the environment already proposed in
production-boundary-proposal.md; this is one more environment (or one more setting). -
Effect: removes the entire "unattended" property that makes the current gap serious. The candidate is still built, signed, and available to test by digest.
-
Trade-off: the daily scheduled build stops auto-publishing. For a repo whose whole point is tracking a moving kernel, that is a real behavioural change —
:latestwould lag until approved. Worth being deliberate about; it may be the correct posture anyway given the maintainer daily-drives the result. -
This needs BOTH a setting and a one-line workflow change. An earlier draft of this document said it was "a repository setting, not a code change". That was wrong. Verified against
build.yml: no job declares anenvironment:key, so creating an environment in repository settings gates nothing on its own -- scheduled runs would keep moving:latestwith no approval, while the settings screen made it look protected. Tier 1 requires:- create the environment with required reviewers (settings; owner only), and
- add
environment: <name>to thepromote-stablejob in.github/workflows/build.yml(a normal pull request).
Adding the key does not interfere with that job's existing
if:expression; the environment gate is evaluated in addition to it.production-boundary-proposal.mdsection 2 already states this correctly for the signing jobs -- the same rule applies here. -
Do not enable "Prevent self-review" on that environment. It is optional and off by default. In a solo-maintainer repository, enabling it means the only reviewer is forbidden from approving, which blocks promotion entirely with no way through except changing the setting back. See "Lockout safety" in
production-boundary-proposal.md.
Boot the candidate image and prove the module loads and versions agree.
Sketch: build a disk image from the candidate with bootc-image-builder, boot it under QEMU on
the runner, and on the booted system assert modprobe zfs succeeds, zfs --version matches
the org.zfs-aurora-complex.zfs-version label, and zpool create on a loopback file works.
Unverified and must be checked before committing to this: whether GitHub-hosted runners
expose /dev/kvm. Without hardware acceleration, QEMU falls back to TCG software emulation and
a full Fedora boot may take long enough to be impractical on a standard runner. This is
cheaply testable with a throwaway workflow that checks for /dev/kvm and times a boot — do
that before designing around it. I have not verified it, and the tier's viability rests on
the answer.
- Cost: a new workflow, a disk-image build step, and a boot harness. Meaningful, and it becomes a maintained thing that can itself break and block promotion.
- Effect: catches "module does not load" and "versions disagree at runtime" automatically, which is most of the practical risk.
Import a real pool, write, scrub, export, and confirm rollback compatibility.
This is where the actual data-loss risk lives (CLAUDE.md rule 5: a newer ZFS line can activate
pool features the previous image cannot import), and it is also the tier CI is worst suited to.
A scratch pool on a loopback file proves the code path but not compatibility with the
maintainer's real pools and their activated feature flags.
Realistic form: a self-hosted runner on the maintainer's hardware, or a documented manual checklist run before accepting a ZFS line bump. Note this is only needed when the ZFS line moves — patch bumps inside a line do not activate new pool features — so it maps naturally to a manual gate rather than every-build automation.
- Tier 1 now. It directly answers the finding, costs almost nothing, and composes with
work already proposed. Decide deliberately about
:latestlagging. - Verify the
/dev/kvmquestion before doing any Tier 2 design work. - Tier 3 as a documented manual gate tied to ZFS line changes, not as CI automation. Automating it badly would produce false confidence, which is worse than an honest checklist.
Adopting all three tiers would not make the pipeline "safe" in an absolute sense. It would move the guarantee from "the image composed and is correctly signed" to "the image composed, is correctly signed, and its ZFS module loaded and handled a pool on at least one machine." That is a real improvement and still not proof for every pool and every kernel. The honest framing stays: CI evidence bounds risk, it does not eliminate it.