BOOTC-SEALED-IMAGES(7)
π¦ BOOTC-SEALED-IMAGES(7) π¦
okay bestie, sit down, hydrate, because bootc-sealed-images is one of those pages that sounds intimidating and is actually so satisfying once it clicks. it's from the Miscellaneous Information Manual, and it's all about sealing your images up with crypto so nobody can mess with them. let's go π
β¨ NAME β¨
bootc-sealed-images β sealed images, explained, with the whole vibe π
πΈ DESCRIPTION πΈ
π« How Sealed Images Work
so a sealed image is a cryptographically signed and verified bootc image that gives you end-to-end integrity protection. like, from firmware all the way to userspace, everything is checked. no cap. it pulls that off with three things working together:
- Unified Kernel Images (UKIs) β it smushes the kernel, the initramfs, and the boot parameters into ONE single signed binary. one file, one signature, iconic.
- Composefs integration β using composefs with fs-verity for content-addressed filesystem verification. content-addressed means the contents themselves prove who they are, which is honestly main character energy.
- Secure Boot β cryptographic signatures on both the UKI and the systemd-boot loader. double-signed, double-safe π«Ά
and a sealed image literally includes these three things:
- composefs digest β a SHA-512 hash of the entire root filesystem, computed at build time. this is your fingerprint of the whole rootfs.
- Unified Kernel Image (UKI) β a single EFI binary containing the kernel, initramfs, and kernel command line, with the composefs digest embedded right in there.
- Secure Boot signature β the UKI gets signed with your private key. π
then at boot time, the composefs digest sitting in the kernel command line (like composefs.digest=v1-sha512-12:<digest>) gets verified against the mounted root filesystem. that's the chain of trust: firmware β userspace, one unbroken link. the system will only boot if the root filesystem matches exactly what was signed. not approximately. exactly. periodt. π
π₯ Building Sealed Images
π Prerequisites
for sealed images, your container has got to:
- include a kernel and initramfs in
/usr/lib/modules/<kver>/ - have systemd-boot available (and NOT have
bootupdβ that's a hard no, bestie) - not include a pre-built UKI (the build process is the one that generates it)
and sealed images also require:
- Secure Boot support in the target system firmware
- a filesystem with fs-verity support (e.g.,
ext4,btrfs) for the root partition
π Using without Secure Boot
you can run a sealed UKI without Secure Boot turned on. the composefs and mounting code is fully orthogonal to Secure Boot β the fs-verity digest of the root filesystem and all of its contents will still be validated at runtime, which does provide an increased level of integrity. so you're still getting something real out of it, lowkey.
However: nothing validates that root digest itself, meaning any locally running code can replace the UKI (e.g. after a container breakout) and fully control the next boot. that's the trade. π
it is intentional to support booting with Secure Boot disabled, because a valid use case is to temporarily disable it in order to test a change locally on e.g. one machine, then re-enable it later. however, at the current time it is not yet streamlined to regenerate the UKI locally. so it's a vibe, but a slightly manual one.
this is independent of --allow-missing-verity (see bootc-composefs(7)), which instead makes fs-verity on the root filesystem optional. different thing, bestie, don't mix them up.
π§ Build Pattern: Split the Kernel, Then Generate the UKI in a Separate Stage
building a sealed image is a three-stage glow-up: build the rootfs, split the kernel and initramfs out of it, then generate the signed UKI from the split rootfs in a tools stage:
# Build your rootfs with all packages and configuration
FROM <base-image> as rootfs
RUN apt|dnf|zypper install ... && bootc container lint --fatal-warnings
# Split the kernel and initramfs out of the rootfs. This moves
# /usr/lib/modules/<kver>/{vmlinuz,initramfs.img} into /kernel/<kver>/,
# since for a sealed image they end up embedded in the UKI instead.
FROM rootfs as split
RUN mkdir /kernel && bootc container split-kernel-and-rootfs --rootfs / --output /kernel
# Generate the sealed UKI in a tools stage
FROM <tools-image> as sealed-uki
RUN --mount=type=bind,from=split,target=/target \
--mount=type=bind,from=split,source=/kernel,target=/kernel \
--mount=type=secret,id=secureboot_key \
--mount=type=secret,id=secureboot_cert <<EORUN
set -euo pipefail
mkdir -p /out
kver=$(ls /kernel)
# `bootc container ukify` computes the composefs digest of /target, reads
# extra kernel arguments from /target/usr/lib/bootc/kargs.d, and invokes the
# real `ukify` binary with the digest embedded in the cmdline. Everything
# after `--` is passed straight through to ukify.
bootc container ukify \
--rootfs /target \
--kernel-dir "/kernel/${kver}" \
-- \
--output "/out/${kver}.efi" \
--signtool sbsign \
--secureboot-private-key /run/secrets/secureboot_key \
--secureboot-certificate /run/secrets/secureboot_cert
EORUN
# Final image: the split rootfs (kernel/initramfs already removed) plus the signed UKI
FROM split
COPY --from=sealed-uki /out/*.efi /boot/EFI/Linux/
this pattern works because β and I'm obsessed with the reasoning here:
bootc container split-kernel-and-rootfsremoves the raw kernel and initramfs from the rootfs ahead of time, so the final image never carries a duplicate copy of them (they end up embedded in the UKI instead). no wasted bytes, we love an efficient queen. β¨bootc container ukifyhandles computing the composefs digest and assembling the kernel command line, so you only need to pass ukify-specific options (like signing) after--. you don't have to think about it.- The final stage copies the signed UKI into the already-split rootfs. clean, simple, done.
π The bootc container ukify Command
bootc container ukify --rootfs <PATH> [OPTIONS] -- [UKIFY_ARGS...]
this is the recommended way to build a UKI for a bootc image. what it does: it computes the composefs digest of --rootfs (using the lower-level compute-composefs-digest primitive described below), reads extra kernel arguments from /usr/lib/bootc/kargs.d, and invokes the system ukify binary with the resulting cmdline. anything after -- is forwarded to ukify unchanged (e.g. --output, --signtool, signing key/cert options). pass-through, no meddling.
options, bestie:
--rootfs <PATH>β root filesystem to operate on (default:/)--kernel-dir <PATH>β directory containingvmlinuz/initramfs.img, named/parent/<kernel-version>. needed when the kernel has already been split out of--rootfs, e.g. viasplit-kernel-and-rootfs--allow-missing-verityβ make fs-verity validation optional, for filesystems that don't support it (e.g. XFS)--erofs-version <v1|v2>βv1(the default) writes a V1 argument followed by a V2 fallback;v2writes only V2. see EROFS formats (seebootc-composefs(7))--write-dumpfile-to <PATH>β write a composefs dumpfile for debugging π
π The bootc container compute-composefs-digest Command
bootc container compute-composefs-digest [PATH]
a lower-level primitive, used internally by ukify above, that computes just the composefs digest for a filesystem without building a UKI. the digest is a 128-character SHA-512 hex string that uniquely identifies the filesystem contents. useful for scripting or debugging outside of the UKI build flow. honestly, great for when you just want the number, y'know?
options:
PATHβ path to the filesystem root (default:/target)--erofs-version <v1|v2>β EROFS format for the computed digest (default:v1)--write-dumpfile-to <PATH>β generate a dumpfile for debugging
see also bootc-container-compute-composefs-digest(8) (see bootc-container-compute-composefs-digest(8)).
π Final Image Structure
your sealed image should have, at the end of it all:
- the signed UKI at
/boot/EFI/Linux/<kver>.efi - a signed systemd-boot at
/boot/EFI/BOOT/BOOTX64.EFIand/boot/EFI/systemd/systemd-bootx64.efi - the raw
vmlinuzandinitramfs.imgremoved from/usr/lib/modules/<kver>/(they're now embedded in the UKI β this is not a bug, it's the whole point π«Ά)
π External Signing Workflow
for production environments with dedicated signing infrastructure:
- Build unsigned UKI β compute digest and create an unsigned UKI (omit
--signtoolfromukify) - Sign externally β take the unsigned UKI to your signing infrastructure
- Complete the seal β inject the signed UKI into the final image
this workflow is planned for streamlining in future releases (see #1498 https://github.com/bootc-dev/bootc/issues/1498).
π SEE ALSO π
β VERSION β
v1.17.1
and that's the whole page, bestie!! π you now know more about sealed images than 99% of people you will ever meet. this was BOOTC-SEALED-IMAGES(7) from the Miscellaneous Information Manual, version v1.17.1 β go forth and seal responsibly, and remember: if the digest doesn't match, it does NOT boot. bye babe πβ¨π¦
π¦ where this page comes from
- fedora 43: bootc 1.17.1-1.fc43 Β· Apache-2.0 AND BSD-3-Clause AND MIT AND (Apache-2.0 OR BSL-1.0) AND (Apache-2.0 OR MIT) AND (Apache-2.0 WITH LLVM-exception OR Apache-2.0 OR MIT) AND (Unlicense OR MIT)
- fedora 44: bootc 1.17.1-1.fc44 Β· Apache-2.0 AND BSD-3-Clause AND MIT AND (Apache-2.0 OR BSL-1.0) AND (Apache-2.0 OR MIT) AND (Apache-2.0 WITH LLVM-exception OR Apache-2.0 OR MIT) AND (Unlicense OR MIT)
- π girlified by deepseek-ai/DeepSeek-V4.1-Flash on : a language model's rewrite of the original, under its terms