GitHub - bootc-dev/bootc: Boot and upgrade via container images

Appears to be a CNCF sandbox project started by Red Hat.

Written in rust

Fundamentally, bootc reuses the OCI image format as a way to transport serialized filesystem trees with included metadata such as a version label, etc.

How does bootc differ from Docker?

Same format, completely different purpose

bootc uses the exact same OCI/Docker image format and tooling (Dockerfiles, podman build, registries, layers, etc.), but instead of packaging an application to run inside a container, you’re packaging a bootable operating system — including the Linux kernel itself (in /usr/lib/modules).

Key differences:

1. What runs at boot Docker runs your process in an isolated namespace with a container runtime as the outer wrapper. With bootc, once deployed to a machine, there is no outer container runtime — systemd is pid 1 directly, just like a normal Linux install. No namespace isolation, no cgroup magic from a runtime daemon.

2. The image includes a kernel A bootc image ships /usr/lib/modules/$kver/vmlinuz. Docker images never have a kernel — they share the host’s. When bootc installs to a machine, that kernel actually boots the hardware.

3. Updates are transactional OS updates, not container restarts bootc uses ostree under the hood to do atomic, transactional, in-place OS upgrades. You bootc upgrade, it fetches the new image, and on next reboot you’re running the updated OS. Rollback is also supported. This is the Fedora Silverblue / CoreOS model generalized.

4. Filesystem layout is different

  • /usr is read-only at runtime (immutable)
  • /etc is machine-local persistent state (with 3-way merge on updates)
  • /var is where mutable runtime data lives
  • Docker containers have no such constraint — the whole FS is writable (via overlay)

5. Docker metadata is largely ignored ENTRYPOINT, CMD, ENV, EXPOSE, USER, HEALTHCHECK — all ignored by bootc at deploy time. The boot process is driven by the bootloader → kernel → initramfs → systemd, not by container metadata.

TL;DR: bootc lets you use the familiar Docker/OCI workflow (Dockerfile, podman build, push to registry) to build and distribute entire operating systems, not just applications. The image format is the same; the runtime model is completely different.

The bootc image is the OS

From the docs fetched earlier:

“At runtime on a target system, the base userspace is not itself running in a container by default. systemd acts as pid1 as usual — there’s no ‘outer’ process.”

What happens at boot:

  1. Bootloader → kernel (from inside the image, /usr/lib/modules/$kver/vmlinuz)
  2. initramfs does an ostree chroot-equivalent into the deployment root
  3. systemd starts as pid1 directly from the image filesystem

There is no host OS underneath. The image is the host. The container format is just the transport mechanism — once installed, the system boots and runs exactly like a conventional Linux install.

The only wrinkle is the physical root vs. logical root distinction: the raw disk’s root is mounted at /sysroot, while the booted OS lives under /sysroot/ostree/deploy/.../. This is an ostree implementation detail, not a second OS — it’s how ostree maintains A/B deployments on a single partition.

how do you use an existing distro if the directory layout is different?

This is a really good question and the honest answer is: it’s complicated, and non-Fedora/RHEL distros are a work in progress.

The core problem

bootc’s requirements push against traditional distro filesystem conventions in three specific ways:

1. Directory layout expectations

bootc/ostree expects the “UsrMerge” layout where /bin, /lib, /sbin etc. are symlinks into /usr. Most modern distros (including Ubuntu 20.04+, Debian Bookworm+, Arch) already do this, so this is mostly a non-issue now. The real layout constraint is that bootc wants:

  • /usr — immutable OS content (read-only at runtime)
  • /etc — mutable config with 3-way merge
  • /var — mutable persistent data

A traditional distro install scatters mutable state across more places and packages often write directly to /opt, /srv, /home during install scripts — things that don’t fit this model cleanly.

2. The bootloader problem (the hard one)

The main problem is that bootupd — which bootc relies on for bootloader management — uses hardcoded RHEL/Fedora-specific paths everywhere. It expects specific grub and shim package layouts, specific .efi file locations, and RPM as the package manager to query file ownership. For non-RPM distros (Debian/Ubuntu/Arch), you’d need to patch bootupd, and the patch is non-trivial. There’s an open tracking issue for generalizing bootupd for non-Fedora distributions. GitHubGitHub

3. The ostree version problem

Ubuntu and Debian currently have too-old an ostree package in their repos — bootc requires at least version 2025.03. So you can’t just apt install bootc on a stock Ubuntu base; you’d need to bring in a newer ostree from somewhere. GitHub

if you’re building something for production, use a Fedora/CentOS/RHEL/Alma base. If you need Ubuntu or Debian semantics badly enough, it’s feasible but you’re taking on integration work that Red Hat engineers haven’t prioritized yet.

1 Like