Architecture
Big picture
bootc is a Cargo workspace. The root manifest declares members = ["crates/*"] (Cargo.toml:2), and the bootc binary is a thin shim over a shared library. The CLI crate does global initialization, builds a single-threaded async runtime, and calls into the library (crates/cli/src/main.rs:20-32). All real logic lives in bootc-lib: command parsing, status, pull, deploy, install, and the two storage backends.
Components
crates/cli
The runnable binary. crates/cli/src/main.rs is deliberately small. run() calls bootc_lib::cli::global_init() before any threads exist (crates/cli/src/main.rs:22), then builds a current-thread Tokio runtime because bootc does little CPU-heavy async work and uses spawn_blocking for explicit threads (crates/cli/src/main.rs:27-30). async_main then hands off to the library entry point (crates/cli/src/main.rs:15).
crates/lib (bootc-lib)
The core. It defines the CLI surface (crates/lib/src/cli.rs), the declarative host model (crates/lib/src/spec.rs), the pull/stage/deploy pipeline (crates/lib/src/deploy.rs), and installation (crates/lib/src/install.rs). It is the crate every other piece depends on.
crates/ostree-ext
A Rust wrapper over libostree that imports and exports between OCI images and ostree commits, including the ManifestDiff used by bootc upgrade --check (crates/lib/src/cli.rs:1250). It is the original storage path.
bootc_composefs backend
A newer backing store under crates/lib/src/bootc_composefs/, with its own boot, update, switch, and rollback modules (boot.rs, update.rs, switch.rs, rollback.rs). The runtime selects it per operation based on the booted storage kind.
How a request flows
Trace bootc upgrade, the operation that fetches a newer version of the current image and queues it for the next boot.
- The binary calls
bootc_lib::cli::run_from_iter(std::env::args())(crates/cli/src/main.rs:15). run_from_iterparses arguments and delegates torun_from_opt(crates/lib/src/cli.rs:1718). Parsing goes throughOpt::parse_including_static, which inspects argv0 so that being invoked asostree-containeror the systemd generator dispatches into internal subcommands (crates/lib/src/cli.rs:1736,crates/lib/src/cli.rs:1750).run_from_optmatchesOpt::Upgrade, acquires storage, and branches onstorage.kind(): the ostree backend callsupgrade, the composefs backend callsupgrade_composefs(crates/lib/src/cli.rs:1771-1780).upgradereads the currentHostviastatus::get_statusand takes the target image fromhost.spec.image(crates/lib/src/cli.rs:1161-1162). A--tagargument derives a new reference from the current image (crates/lib/src/cli.rs:1165-1172).- Local rpm-ostree modifications block the upgrade with an error (
crates/lib/src/cli.rs:1183-1187). With--check, bootc prepares the import without fetching and prints aManifestDiff(crates/lib/src/cli.rs:1231-1252). - Otherwise it fetches:
pull_unifiedwhen the image already exists in unified storage,pullotherwise (crates/lib/src/cli.rs:1256-1277). - It compares the fetched digest against the staged and booted digests (
crates/lib/src/cli.rs:1278-1289). If nothing changed it printsNo update available.; if there is a new image it callsdeploy::stage(crates/lib/src/cli.rs:1325,crates/lib/src/cli.rs:1329). stagerecords a journal entry and emits a three-step progress sequence (merging, deploying, bound images) while callingdeploy, then pulls logically bound images (crates/lib/src/deploy.rs:1075-1099).
Key design decisions
The host is modeled as a Kubernetes-style declarative object. spec.rs sets API_VERSION = "org.containers.bootc/v1" and KIND = "BootcHost" (crates/lib/src/spec.rs:18-19), and the Host struct flattens a k8s Resource to carry apiVersion, kind, and metadata, then separates spec (intent) from status (observed) (crates/lib/src/spec.rs:29-35). bootc edit is described in its help as operating like kubectl apply, honoring only changes to the spec section (crates/lib/src/cli.rs:891-899).
Updates are A/B and pull-based. The Upgrade help documents that an update does not touch the running system, becomes visible as staged in bootc status, and is applied at shutdown via ostree-finalize-staged.service unless you pass --apply (crates/lib/src/cli.rs:842-855). switch is described as almost the same operation as upgrade but also changing the image reference, with the common pattern being a management agent that controls updates through image tags (crates/lib/src/cli.rs:856-866).
The runtime stays out of the container. Per the README, the kernel ships in the image and boots the host, but the running userspace is not in a container and systemd is pid1 (README.md:14-17). The container is the delivery format only.
Extension points
- Install configuration: drop a TOML file under
/usr/lib/bootc/install/to set defaults such as the root filesystem type, merged in alphanumeric order (documented indocs/src/bootc-install.md). - Bootloader installation is delegated to the external
bootupdproject, invoked bybootc install(docs/src/bootc-install.md). - Logically bound images: images referenced by the host image are pulled during staging via
boundimage::pull_bound_images(crates/lib/src/deploy.rs:1099). - Storage backends: the ostree-container and composefs stores are selected via
storage.kind()(crates/lib/src/cli.rs:1773).