composefs backend
July 10, 2026 · View on GitHub
Experimental features are subject to change or removal. Please do provide feedback on them.
Overview
The composefs backend is an experimental alternative storage backend that uses composefs-rs instead of ostree for storing and managing bootc system deployments.
Status: Experimental, but close to stabilization! We are committed to in-place upgrades from all systems deployed since bootc 1.16.0.
The composefs backend supports two distinct levels of integrity guarantee, controlled by whether fsverity is strictly enforced on the root filesystem (i.e. whether the image was built with --allow-missing-verity):
- Sealed: The composefs digest is baked into the kernel command line of a UKI and required to match at boot.
- Unsealed: fsverity enforcement is optional, so composefs still provides content-addressed, deduplicated storage and garbage collection, but without a guarantee that the root filesystem matches what was signed. Unsealed composefs most commonly boots via a traditional
vmlinuz/initramfs.imgand a BLS boot entry, but a UKI built with--allow-missing-verityis also unsealed in this sense — packaging as a UKI is a boot convenience here, not by itself a security boundary. See Bootloader Support below.
Storage and repository structure
Unlike the ostree backend, which keeps its repository at /ostree/repo, the composefs backend splits its on-disk state across two top-level directories in the physical sysroot:
/composefs: The composefs-rs repository (mode0700), containing:objects/: content-addressed file storage, keyed by SHA-512 fsverity digest and shared via reflink (FICLONE) where the filesystem supports itimages/: EROFS images describing each deployment's root filesystem metadatastreams/: OCI manifest, config, and layer splitstreams captured during image pullsbootc/storage/: thecontainers-storage:instance backing logically bound images, reflink-shared with the composefs object store
/state/deploy/<deployment-id>/: Persistent per-deployment state, one directory per deployment (named after its composefs digest):etc/: a writable copy of the deployment's/etc, bind-mounted onto the booted root's/etcvar: a symlink to the shared/state/os/default/var, bind-mounted onto the booted root's/var<deployment-id>.origin: an INI file recording the image reference, boot type (BLS or UKI) and digest, and the OCI manifest digest (the latter is what keeps a deployment's objects alive across garbage collection)
Although composefs-rs supports other fsverity hash algorithms, bootc currently hardcodes SHA-512 for the repository (see Algorithm::SHA512 at every repository init/open call site, and the ComposefsRepository type alias in crates/lib/src/store/mod.rs). This is why deployment and object identifiers throughout this document (and in bootc status) are 128-character hex strings.
There is no /ostree/repo; the composefs backend doesn't use the ostree repository at all. A minimal /ostree directory is still created, but only to hold a compatibility symlink (ostree/bootc -> ../composefs/bootc) so that existing tooling expecting /usr/lib/bootc/storage to resolve through ostree/bootc keeps working.
Transient, not-yet-finalized deployment state (used while staging an update before reboot) lives under /run/composefs/staged-deployment and is never persisted to disk.
How Sealed Images Work
A sealed image is a cryptographically signed and verified bootc image that provides end-to-end integrity protection. This is achieved through:
- Unified Kernel Images (UKIs): Combining kernel, initramfs, and boot parameters into a single signed binary
- Composefs integration: Using composefs with fsverity for content-addressed filesystem verification
- Secure Boot: Cryptographic signatures on both the UKI and systemd-boot loader
A sealed image includes:
- composefs digest: A SHA-512 hash of the entire root filesystem, computed at build time
- Unified Kernel Image (UKI): A single EFI binary containing the kernel, initramfs, and kernel command line with the composefs digest embedded
- Secure Boot signature: The UKI is signed with your private key
At boot time, the composefs digest in the kernel command line (e.g., composefs=<sha512-hash>) is verified against the mounted root filesystem. This creates a chain of trust from firmware to userspace, ensuring the system will only boot if the root filesystem matches exactly what was signed.
Building Sealed Images
Prerequisites
For sealed images, the container must:
- Include a kernel and initramfs in
/usr/lib/modules/<kver>/ - Have systemd-boot available (and NOT have
bootupd) - Not include a pre-built UKI (the build process generates one)
Sealed images also require:
- Secure Boot support in the target system firmware
- A filesystem with fsverity support (e.g., ext4, btrfs) for the root partition
Using without Secure Boot
You can use a sealed UKI without Secure Boot enabled. The composefs and mounting code is fully orthogonal to Secure Boot - the fsverity digest of the root filesystem and all of its contents will still be validated at runtime, which does provide an increased level of integrity.
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.
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.
Note this is a different, independent weakening from --allow-missing-verity
(see Overview above): disabling Secure Boot only removes firmware
verification of the UKI's own signature, while the fsverity digest of the root
filesystem is still enforced. Building with --allow-missing-verity instead
disables that digest enforcement itself, which is what actually makes an image
unsealed regardless of whether Secure Boot is enabled.
Build Pattern: Split the Kernel, Then Generate the UKI in a Separate Stage
Building a sealed image involves three stages: build the rootfs, split the kernel and initramfs out of it, and 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:
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)bootc container ukifyhandles computing the composefs digest and assembling the kernel command line, so you only need to passukify-specific options (like signing) after--- The final stage copies the signed UKI into the already-split rootfs
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. 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).
Options:
--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 fsverity validation optional, for filesystems that don't support it (e.g. XFS)--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.
Options:
PATH: Path to the filesystem root (default:/target)--write-dumpfile-to <PATH>: Generate a dumpfile for debugging
Note: This command is currently hidden from
--helpoutput as it's part of the experimental composefs feature set.
Final Image Structure
The sealed image should have:
- 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)
External Signing Workflow
For production environments with dedicated signing infrastructure:
- Build unsigned UKI: Compute digest and create an unsigned UKI (omit
--signtoolfrom ukify) - 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).
Developing and Testing bootc with composefs
See CONTRIBUTING.md for information on building and testing bootc itself with composefs support.
Bootloader Support
Whenever the container image has a UKI, bootc automatically selects the composefs backend during installation (see Prerequisites above for the currently-supported UKI + systemd-boot configuration for building sealed images). Note that having a UKI does not by itself make an install sealed — that also depends on whether fsverity enforcement is on, per Overview above.
Composefs installs using a traditional vmlinuz/initramfs.img layout instead of a UKI are always unsealed, and can use either bootupd (GRUB) or systemd-boot, the same as the ostree backend. See bootloaders.md for the general bootloader selection rules. Under the hood, bootc writes standard BLS boot entries for both UKI and traditional kernels; see the composefs boot module documentation for details on how entry filenames and sort-keys are chosen to sort correctly on both GRUB and systemd-boot.
Installation
There is a --composefs-backend option for bootc install to explicitly select a composefs backend apart from sealed images; this is not as heavily tested yet.
Known Issues
The composefs backend is experimental; on-disk formats are subject to change.
Important
- Extended install APIs: Ability to cleanly implement anaconda %post and osbuild post mutations and general post-install pre-reboot; right now some tools just mount the deployment directory (note this one also relates to APIs in general)
- Dual EROFS v1/v2 generation:
bootc installcurrently generates both EROFS v1 and v2 images for every deployment, since v1 is required on older kernels (e.g. RHEL 9/CentOS Stream 9) while v2 is the modern native format. The overhead is small since EROFS images only hold metadata, but this dual generation is a stopgap until we can drop v1 support.
Related issues
- Unified storage: Not strictly a blocker but a really nice to have
- Sealed image build UX: Streamlined tooling for building sealed images
- In place transitions:
- First: support factory reset from ostree to composefs
- Next: Support copying /etc and /var
Additional Resources
- See filesystem.md for information about composefs in the standard ostree backend
- See bootloaders.md for bootloader configuration details
- composefs-rs - The underlying composefs implementation
- composefs-rs repository format - Detailed on-disk layout of the
/composefsrepository - Unified Kernel Images specification
- ukify documentation - Tool for building UKIs