bootc: an OCI image as the delivery format for a host operating system
Boot and upgrade via container images
At a glance
- What is it?
- bootc is a Rust client that boots a Linux host from a container image and updates it in place, and the detail that matters most is the one the README corrects first, which is that the host is not running inside a container at boot, systemd is pid1 as usual with no outer process. The implementation sits on composefs and erofs, with a loopback fallback for kernels that cannot mount an image from a file descriptor.
- Who is it for?
- bootc suits an organisation that already builds with containers and wants host updates to arrive as an image reference, because the delivery model is the part that changes how you work, and the mechanism underneath is a composition filesystem rather than a package manager or a dual-boot scheme. Check three things before committing.
- Can I use it commercially?
- Check first. The repository uses a licence we do not classify automatically, so read its LICENSE file before any commercial use.
- Is it still maintained?
- Yes. The repository received new commits within the last day.
- What is it written in?
- Mainly Rust, according to GitHub's language statistics.
Answers come from the project's GitHub data, last synced on October 1, 2026, and from our analysis. They are not legal advice.
Editorial analysis
The host is not in a container, and that is the point
The most useful paragraph in this README is the one correcting a misunderstanding. The container image includes a Linux kernel, in a directory such as the modules directory under the user tree, and that kernel is what boots. At runtime on the target system, the base userspace is not itself running inside a container by default, and the example given is that systemd, assuming it is in use, acts as pid1 as usual, with no outer process. So bootc is not a container runtime with a small operating system in the middle, and it is not a shell that starts containers at boot. It takes a delivery format that the container ecosystem already knows how to build, store, sign, mirror and refer to by digest, and uses it to describe a complete host, and then the host behaves like a host. The motivation section makes the analogy explicit, noting that the layered model was extremely successful for applications and that the aim is to apply the same technique to bootable host systems. Everything else in the project follows from taking that seriously. Because an image reference is the unit of deployment, the questions of what changed and whether what you asked for is what you got are answered by the registry rather than by a package list, and the README's insistence that the client is not tied to any particular operating system or distribution is a statement about the same idea from the other direction. It is a client system, and the distribution decision belongs to whoever builds the image.
composefs underneath, erofs on disk, and a loopback fallback for a 5.14 kernel
The mechanism is visible in the dependency manifest rather than in prose, which is convenient, because the manifest comments explain the decisions. The central dependency is not a Rust crate from a registry but a git checkout of the composefs control library, pinned to a release tag, pulled in with a specific set of features: OCI, containers storage, FUSE, and two compatibility features named for kernel versions below 6.15 and below 6.16. So the image filesystem is read-only and erofs based, the composition of the deployment is done by composefs, and the two pre-version features exist to run on kernels that predate the relevant kernel interfaces. The Makefile then supplies the sharpest compatibility fact in the repository. It auto-detects the build environment and, when it finds a RHEL-like distribution, enables an RHEL feature, and on RHEL or CentOS Stream 9 specifically, which runs kernel 5.14, it enables a further feature whose stated reason is that this kernel cannot mount an erofs image directly from a file descriptor, so the control library activates a loopback device fallback instead. Read that twice, because it is a real behavioural difference rather than a build nuisance. On a current kernel the image is mounted from a file descriptor, and on a 5.14 kernel a loop device is involved, and the code has to be built for the right one. The build detects the host it is running on, which means a binary built on a current workstation and shipped to an older fleet is the wrong binary, which is a packaging decision rather than a code one.
composefs-ctl = { git = "https://github.com/composefs/composefs-rs", tag = "v0.9.2", default-features = false, features = [
"pre-6.15",
"pre-6.16",
"oci",
"containers-storage",
"fuse",
] }The remote pull feature is off because a transitive dependency's licences are not allowed
One comment in the dependency list is the most revealing governance note in the project, and it concerns a feature that is currently disabled. The remote-fetch capability of the composition library, whose feature is named for the older image update system, is switched off, and the comment gives the reason. Enabling it pulls in an HTTP client for remote fetches, which in turn drags in a chain of certificate and trust related crates, and whose licences are not in the project's cargo-deny allow list. The instruction is to re-enable it once that is sorted, and there is a pull request reference for the work. Read carefully, that means the composition library's own remote fetch is not how bootc obtains an image, and it also means the allow list is enforced rather than decorative, since a transitive dependency of a transitive dependency was enough to switch a feature off and leave a note. For an adopter this is a positive signal about how the dependency surface is treated and a specific thing to track, because a project that will not ship a feature until its licence position is clean is one whose future releases can change what is compiled in without a major version. The same discipline shows up elsewhere in the manifest, where a manifest generator for command line documentation is a dependency, and where the release profile aborts on panic, with the comment explaining that the project uses foreign function interfaces so this is the safest setting. Both are the kind of small decision that is cheap to make once and expensive to reverse.
Three build profiles, and the one measurement in the repository
The manifest carries four profile sections, and the custom ones are where the engineering taste shows. The development profile sets an optimisation level of one, with the comment that no optimisations are too slow for the developers, which is an unusual and welcome admission that a debug build of a system component has to be usable. The release profile uses thin link time optimisation, aborts on panic as noted, and, in the line that surprises people, sets debug information to true with the comment that the project assumes delivery through a packaging system such as RPM that supports split debugging information. So the shipped binary carries symbols and the packaging step is expected to split them out, which is a distribution decision with a real payoff for anyone debugging a crash report from a deployed host. The custom thin profile exists for the case where that packaging is not available. Its comment contains the only measured numbers in the repository: a build that was 140 megabytes, reduced to 12 megabytes without symbols or debug information, and to 5.8 megabytes with the extra optimisations, using full link time optimisation, stripping, size oriented optimisation and a single codegen unit. For a component that is copied into a host image, a 5.8 megabyte binary against a 140 megabyte one is the difference between shipping the tool in every image and shipping it on demand.
[profile.thin]
inherits = "release"
debug = false
strip = true
lto = true
opt-level = 's'
codegen-units = 1The Dockerfile builds bootc from a bootc image, with a caching strategy
The container build is worth reading for three reasons, and the first is the base image argument. Its default is a bootc-based CentOS Stream image pulled from a public registry, which means the project builds itself on the delivery format it implements, and it does so by default rather than as an experiment. That sits in mild tension with the stated position that the client is not tied to any particular distribution, and the resolution is that the build base is a choice while the runtime is not. The second reason is the staging. The build declares two intermediate stages from an empty base, one that copies the whole source tree and one that copies only the packaging directory, and the comment on the second says it exists to get more precise cache hits, which is a small thing that saves a great deal of time in a project where any change to a source file would otherwise invalidate the dependency build. The build stage is separate from the final one, with the intention stated explicitly: the target root filesystem is extracted from the build output so that the build dependencies are not part of what ships. The third reason is the housekeeping, and there are three examples of it. The run and temporary directories are mounted as in-memory filesystems with bind mounts inside them, with a comment saying this avoids leaking mount stubs into the image. There is a build argument that turns off initramfs code, described as flipping it off to disable that code path. And there is a build argument for incremental compilation that continuous integration passes to save disk in the build cache, with the comment noting that the cargo default applies locally when it is unset. A project that explains its cache invalidation strategy in the Dockerfile is a project whose maintainers have thought about what their own development loop costs.
A stable CLI, an in-place upgrade promise, and a weekly patch line
The status section is short and it is the most reassuring part of the document. The command line interface and the API are described as considered stable, and the project commits to ensuring that every existing system can be upgraded in place across any future changes. That is a specific and unusual promise to make about an operating system component, and it is the commitment that makes an in-place update model safe to adopt, because the failure mode everyone fears is a fleet that cannot take the next update because the update mechanism itself changed. The versioning section is equally concrete. The project is not published to the Rust registry as a library, and version numbers are expected to follow semantic versioning, with the practice beginning at version 1.2.0 and versions before that not necessarily adhering. The release record matches: 1.16.11 on 2026-09-03, 1.16.12 on 2026-09-10 and 1.16.13 on 2026-09-15, so a patch roughly every week on a stable minor line, which is the cadence you want from something that sits between a registry and a boot. The last push was on 2026-09-28, so both the code and the release line are current. Two structural points go with it. The project is a Cloud Native Computing Foundation sandbox project, which is the first step of that foundation's maturity ladder rather than a judgement about the code, and it has its own governance document, maintainers list, code of conduct, meetings directory and a security policy. The distribution question is delegated to an adopters file, which the README calls the place to start, and that is where you find out which operating systems have images built for them rather than whether the client can talk to yours.
Two licence files, a Makefile forbidden from sudo, and VM tests
A few repository details close out the picture. The licence is a set of files, one general and one each for the Apache and the MIT texts, which is the conventional dual grant that Rust projects adopt and which is also why the forge reports the licence as one it cannot classify. Nothing about it is unusual, and the practical position is that you may use the code under either of the two terms. Alongside it sits a configuration file for dependency licence and advisory checking, the linter configuration, a dependency update automation file, and a packaging configuration for the Fedora build service, so the housekeeping around a system component is in the tree rather than in a maintainer's head. The Makefile is the most unusual file to read, because it explains what it refuses to do. Its comment says the file must not spawn container or virtualization tools and must not invoke sudo, and that code run from it is expected to be inside a low-privilege container, running as a nonzero user, and that anything requiring virtual machines belongs in the Justfile instead, with a further note to use a Rust task program rather than bash if the automation would otherwise become a thicket of shell. The testing story is consistent with that split, with a directory of tests driven by a testing framework that uses distribution metadata for describing cases, which is how virtual machine based integration tests are usually written. Two smaller observations. The discussion forum link in the README still points at the project under a different organisation path from the repository you are reading, which works by redirection and suggests the document has not been revisited since the move. And the tree carries agent instruction files for several tools at once, alongside a file that records the last commit from the project's own infrastructure repository, which is how a build gets invalidated when something outside the tree changes.
Editorial conclusion
bootc suits an organisation that already builds with containers and wants host updates to arrive as an image reference, because the delivery model is the part that changes how you work, and the mechanism underneath is a composition filesystem rather than a package manager or a dual-boot scheme. Check three things before committing. The kernel requirement, since a host on a 5.14 kernel takes the loopback path rather than mounting the image from a file descriptor, and the build detects that for you but the runtime behaviour differs. The upgrade path for your fleet, because the stability promise covers in-place upgrades of existing systems rather than first installs, so test the jump on the oldest image you actually run. And the licence position, which is a dual grant of MIT and Apache with a dependency allow list enforced by a configuration file in the tree, and which is strict enough that a transitive dependency was enough to switch a feature off. Read the two feature lists in the manifest before assuming which filesystem and which remote-fetch paths are compiled in.
Frequently asked questions
Does bootc run the host operating system inside a container?
No. The README is explicit that the base userspace is not itself running in a container at runtime, and that systemd acts as pid1 as usual with no outer process. The container image carries the kernel that boots and is used as the delivery format, and the host behaves like a host.
What happens on a host running an older kernel?
The Makefile auto-detects the build environment and enables an extra feature on RHEL and CentOS Stream 9, which run kernel 5.14, because that kernel cannot mount an erofs image directly from a file descriptor, so the composition library falls back to a loopback device. A binary built on a current machine and shipped to an older fleet would be the wrong binary.
Is bootc's update path stable enough to adopt?
The project states that the command line interface and API are considered stable, and commits to ensuring that every existing system can be upgraded in place across any future changes. Version numbers follow semantic versioning from 1.2.0 onward, and the recent releases are weekly patches on the 1.16 line.
Why is the remote fetch feature of the composition library disabled?
The comment in the dependency manifest says enabling it pulls in an HTTP client for remote fetches, which brings in certificate and trust related crates whose licences are not in the project's dependency allow list. The instruction is to re-enable it once that is sorted, and a pull request is referenced for the work.
What licence is bootc under?
The repository ships a general licence file alongside separate Apache and MIT licence texts, which is the conventional dual grant for a Rust project and the reason the licence is reported as one that cannot be classified. The code may be used under either set of terms.
Official sources
Add this badge to your README
If you maintain this project, the badge below links readers to this analysis and shows its maintenance status from the daily GitHub snapshot. Paste the markdown into your README; add ?metric=license or ?metric=stars to the image URL for a different field.
[](https://hysenlabs.com/projects/bootc-dev-bootc)