Skip to content

Commit 332e36f

Browse files
committed
docs/composefs: Update with latest
In particular, this commits us to supporting in-place upgrades from the current state! We will leave things like the EROFS v1 switch as a followon, ensuring that we continue to support that as an upgrade path. Signed-off-by: Colin Walters <walters@verbum.org>
1 parent bbfcc84 commit 332e36f

1 file changed

Lines changed: 85 additions & 45 deletions

File tree

docs/src/experimental-composefs.md

Lines changed: 85 additions & 45 deletions
Original file line numberDiff line numberDiff line change
@@ -5,14 +5,34 @@ do provide feedback on them.
55

66
## Overview
77

8-
The composefs backend is an experimental alternative storage backend that uses [composefs-rs](https://github.com/containers/composefs-rs) instead of ostree for storing and managing bootc system deployments.
8+
The composefs backend is an experimental alternative storage backend that uses [composefs-rs](https://github.com/composefs/composefs-rs) instead of ostree for storing and managing bootc system deployments.
99

10-
**Status**: Experimental. The composefs backend is under active development and not yet suitable for production use. The feature is always compiled in as of bootc v1.10.1.
10+
**Status**: Experimental, but close to stabilization! We are committed to in-place upgrades from all systems deployed since bootc 1.16.0.
1111

12-
A key goal is custom "sealed" images, signed with your own Secure Boot keys.
13-
This is based on [Unified Kernel Images](https://uapi-group.org/specifications/specs/unified_kernel_image/)
14-
that embed a digest of the target container root filesystem, typically alongside a bootloader (such
15-
as systemd-boot) also signed with your key.
12+
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`):
13+
14+
- **Sealed**: The composefs digest is baked into the kernel command line of a UKI and *required* to match at boot.
15+
- **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.img` and a BLS boot entry, but a UKI built with `--allow-missing-verity` is *also* unsealed in this sense — packaging as a UKI is a boot convenience here, not by itself a security boundary. See [Bootloader Support](#bootloader-support) below.
16+
17+
## Storage and repository structure
18+
19+
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:
20+
21+
- `/composefs`: The [composefs-rs repository](https://github.com/composefs/composefs-rs/blob/main/crates/composefs/src/repository_format.rs) (mode `0700`), containing:
22+
- `objects/`: content-addressed file storage, keyed by SHA-512 fsverity digest and shared via reflink (`FICLONE`) where the filesystem supports it
23+
- `images/`: EROFS images describing each deployment's root filesystem metadata
24+
- `streams/`: OCI manifest, config, and layer splitstreams captured during image pulls
25+
- `bootc/storage/`: the `containers-storage:` instance backing logically bound images, reflink-shared with the composefs object store
26+
- `/state/deploy/<deployment-id>/`: Persistent per-deployment state, one directory per deployment (named after its composefs digest):
27+
- `etc/`: a writable copy of the deployment's `/etc`, bind-mounted onto the booted root's `/etc`
28+
- `var`: a symlink to the shared `/state/os/default/var`, bind-mounted onto the booted root's `/var`
29+
- `<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)
30+
31+
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.
32+
33+
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.
34+
35+
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.
1636

1737
## How Sealed Images Work
1838

@@ -61,59 +81,86 @@ valid use case is to temporarily disable it in order to test a change locally
6181
on e.g. one machine, then re-enable it later. However at the current time it
6282
is not yet streamlined to regenerate the UKI locally.
6383

64-
### Build Pattern: Compute Digest and Generate UKI in One Stage
84+
Note this is a different, independent weakening from `--allow-missing-verity`
85+
(see [Overview](#overview) above): disabling Secure Boot only removes firmware
86+
verification of the UKI's own signature, while the fsverity digest of the root
87+
filesystem is still enforced. Building with `--allow-missing-verity` instead
88+
disables that digest enforcement itself, which is what actually makes an image
89+
unsealed regardless of whether Secure Boot is enabled.
90+
91+
### Build Pattern: Split the Kernel, Then Generate the UKI in a Separate Stage
6592

66-
The key to building sealed images is using a multi-stage Dockerfile where a separate stage mounts the target rootfs, computes its composefs digest, and generates the signed UKI in one step:
93+
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:
6794

6895
```dockerfile
6996
# Build your rootfs with all packages and configuration
7097
FROM <base-image> as rootfs
7198
RUN apt|dnf|zypper install ... && bootc container lint --fatal-warnings
7299

100+
# Split the kernel and initramfs out of the rootfs. This moves
101+
# /usr/lib/modules/<kver>/{vmlinuz,initramfs.img} into /kernel/<kver>/,
102+
# since for a sealed image they end up embedded in the UKI instead.
103+
FROM rootfs as split
104+
RUN mkdir /kernel && bootc container split-kernel-and-rootfs --rootfs / --output /kernel
105+
73106
# Generate the sealed UKI in a tools stage
74107
FROM <tools-image> as sealed-uki
75-
RUN --mount=type=bind,from=rootfs,target=/target \
108+
RUN --mount=type=bind,from=split,target=/target \
109+
--mount=type=bind,from=split,source=/kernel,target=/kernel \
76110
--mount=type=secret,id=secureboot_key \
77111
--mount=type=secret,id=secureboot_cert <<EORUN
78112
set -euo pipefail
79113

80-
# Compute the composefs digest from the mounted rootfs
81-
digest=$(bootc container compute-composefs-digest /target)
82-
83-
# Find the kernel version
84-
kver=$(ls /target/usr/lib/modules)
85-
86-
# Generate and sign the UKI with the digest embedded
87-
ukify build \
88-
--linux "/target/usr/lib/modules/${kver}/vmlinuz" \
89-
--initrd "/target/usr/lib/modules/${kver}/initramfs.img" \
90-
--cmdline "composefs=${digest} rw" \
91-
--os-release "@/target/usr/lib/os-release" \
114+
mkdir -p /out
115+
kver=$(ls /kernel)
116+
117+
# `bootc container ukify` computes the composefs digest of /target, reads
118+
# extra kernel arguments from /target/usr/lib/bootc/kargs.d, and invokes the
119+
# real `ukify` binary with the digest embedded in the cmdline. Everything
120+
# after `--` is passed straight through to ukify.
121+
bootc container ukify \
122+
--rootfs /target \
123+
--kernel-dir "/kernel/${kver}" \
124+
-- \
125+
--output "/out/${kver}.efi" \
92126
--signtool sbsign \
93127
--secureboot-private-key /run/secrets/secureboot_key \
94-
--secureboot-certificate /run/secrets/secureboot_cert \
95-
--output "/out/${kver}.efi"
128+
--secureboot-certificate /run/secrets/secureboot_cert
96129
EORUN
97130

98-
# Final image: copy the sealed UKI into place
99-
FROM rootfs
131+
# Final image: the split rootfs (kernel/initramfs already removed) plus the signed UKI
132+
FROM split
100133
COPY --from=sealed-uki /out/*.efi /boot/EFI/Linux/
101134
```
102135

103136
This pattern works because:
104137

105-
1. The `--mount=type=bind,from=rootfs` provides read-only access to the target filesystem
106-
2. `bootc container compute-composefs-digest` computes the SHA-512 hash of the rootfs
107-
3. `ukify` creates the UKI with that digest in the kernel command line (`composefs=<digest>`)
108-
4. The final stage copies the signed UKI into the rootfs without modifying any files used in the digest calculation
138+
1. `bootc container split-kernel-and-rootfs` removes 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)
139+
2. `bootc container ukify` handles computing the composefs digest and assembling the kernel command line, so you only need to pass `ukify`-specific options (like signing) after `--`
140+
3. The final stage copies the signed UKI into the already-split rootfs
141+
142+
### The `bootc container ukify` Command
143+
144+
```bash
145+
bootc container ukify --rootfs <PATH> [OPTIONS] -- [UKIFY_ARGS...]
146+
```
147+
148+
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).
149+
150+
**Options:**
151+
152+
- `--rootfs <PATH>`: Root filesystem to operate on (default: `/`)
153+
- `--kernel-dir <PATH>`: Directory containing `vmlinuz`/`initramfs.img`, named `/parent/<kernel-version>`. Needed when the kernel has already been split out of `--rootfs`, e.g. via `split-kernel-and-rootfs`
154+
- `--allow-missing-verity`: Make fsverity validation optional, for filesystems that don't support it (e.g. XFS)
155+
- `--write-dumpfile-to <PATH>`: Write a composefs dumpfile for debugging
109156

110157
### The `bootc container compute-composefs-digest` Command
111158

112159
```bash
113160
bootc container compute-composefs-digest [PATH]
114161
```
115162

116-
Computes the composefs digest for a filesystem. The digest is a 128-character SHA-512 hex string that uniquely identifies the filesystem contents.
163+
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.
117164

118165
**Options:**
119166

@@ -146,7 +193,9 @@ See [CONTRIBUTING.md](https://github.com/bootc-dev/bootc/blob/main/CONTRIBUTING.
146193

147194
## Bootloader Support
148195

149-
To use sealed images, the container image must have a UKI and systemd-boot installed (and not have `bootupd`). If these conditions are met, bootc will automatically detect and use the composefs backend during installation.
196+
Whenever the container image has a UKI, bootc automatically selects the composefs backend during installation (see [Prerequisites](#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](#overview) above.
197+
198+
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](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](https://github.com/bootc-dev/bootc/blob/main/crates/lib/src/bootc_composefs/boot.rs) for details on how entry filenames and sort-keys are chosen to sort correctly on both GRUB and systemd-boot.
150199

151200
## Installation
152201

@@ -156,33 +205,24 @@ There is a `--composefs-backend` option for `bootc install` to explicitly select
156205

157206
The composefs backend is experimental; on-disk formats are subject to change.
158207

159-
### Deployment blockers
208+
### Important
160209

161-
- [Garbage collection](https://github.com/bootc-dev/bootc/pull/2040): In progress
162210
- 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](https://github.com/bootc-dev/bootc/issues/522))
163-
- [OCI registry install](https://github.com/bootc-dev/bootc/issues/1703): Installing from registry can fail due to config mismatch (suggestion: just clean reject v2s2)
164-
- [composefs-rs repository finalization](https://github.com/bootc-dev/bootc/issues/1320)
211+
- [Dual EROFS v1/v2 generation](https://github.com/bootc-dev/bootc/pull/2248): `bootc install` currently 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.
165212

166-
### Important
213+
## Related issues
167214

168-
- Extended test suite: Right now we're not covering upgrades well, need to build upgrade image in sealed cases
169-
- Full workflow test - add composefs into https://gitlab.com/fedora/bootc/tests/bootc-workflow-test for example
170-
- Workflow upgrades especially "from old systems"
171215
- [Unified storage](https://github.com/bootc-dev/bootc/issues/20): Not strictly a blocker but a really nice to have
172216
- [Sealed image build UX](https://github.com/bootc-dev/bootc/issues/1498): Streamlined tooling for building sealed images
173217
- In place transitions:
174218
- First: support [factory reset](https://github.com/bootc-dev/bootc/issues/404) from ostree to composefs
175219
- Next: Support copying /etc and /var
176-
- A lot more practical level docs for using this
177-
178-
### Minor
179-
180-
- Remove `/usr/lib/bootc/kargs.d` as part of UKI creation (also `bootc container inspect` should show UKI kargs)
181220

182221
## Additional Resources
183222

184223
- See [filesystem.md](filesystem.md) for information about composefs in the standard ostree backend
185224
- See [bootloaders.md](bootloaders.md) for bootloader configuration details
186-
- [composefs-rs](https://github.com/containers/composefs-rs) - The underlying composefs implementation
225+
- [composefs-rs](https://github.com/composefs/composefs-rs) - The underlying composefs implementation
226+
- [composefs-rs repository format](https://github.com/composefs/composefs-rs/blob/main/crates/composefs/src/repository_format.rs) - Detailed on-disk layout of the `/composefs` repository
187227
- [Unified Kernel Images specification](https://uapi-group.org/specifications/specs/unified_kernel_image/)
188228
- [ukify documentation](https://www.freedesktop.org/software/systemd/man/latest/ukify.html) - Tool for building UKIs

0 commit comments

Comments
 (0)