Introduction
If your goal is simpler, more reliable DevOps pipelines, start by making the root filesystem read-only. That one constraint eliminates configuration drift, makes fleet state auditable, and turns upgrades into safe, atomic operations. The common worry is that this adds complexity; in practice, bootc (Bootable Containers) simplifies workflows resulting in repeatable builds you can test before rollout.
With bootc, your “system image” is built like an application container: declarative layers, reproducible builds, and the exact same artifact promoted from dev to prod. The result:
- Immutable, read-only root: no ad-hoc changes, no surprise drift
- Atomic upgrades and easy rollbacks
- Consistent testing: validate the full system image before deployment
- Clear separation of configuration: config and state live in well-defined writable locations, everything else is image-defined
This post walks through a minimal workflow to get to real, usable read-only root filesystems:
- Create a baseline bootc image with a read-only root
- Layer a specialized bootc image for your workload
- Convert the bootc image into a bootable system
- Exercise an upgrade and rollback to prove atomicity and reliability
Trade uncertainty and chaos for predictability by embracing read-only root filesystems with bootc!
What are Bootable Containers ( Bootc )
Bootable containers enable Linux system admins to transactionally update and rollback entire systems using OCI (Open Container Initiative) container images. Instead of using traditional OS images, with bootc, the operating system and its root filesystem are built as a container image. Bootable Containers share many of the attributes of Application Containers, but include an init system, kernel, and complete userspace so that they can be independently booted. Bootc leverages existing and established technologies like podman and ostree to provide a smooth and predictable workflow for managing your fleet of images and systems. To update a bootc-powered installed system existing OCI transport “on the wire” is used to ingest new container images as ostree-layered updates for your system. From a user’s perspective, a bootc-powered system is still an ostree-managed system, providing an experience that is almost unchanged compared to ostree-native systems. The key difference is that the transport mechanism for updates is the “on-the-wire” OCI mechanism, which tracks the corresponding container images from which the system was deployed, ingests those container images, and layers them as ostree layered updates. Significantly, the system can be transactionally rolled back to a previous state. Generally in the world of bootc you will want to have a clean baseline container, which is a very basic system, with kernel-uek install (UEKR8) and a minimal userspace, including bootc as rpm package. That baseline container in order can then be used to build a variety of customized containers, serving your purpose.
Bootable Containers and Bootable Systems
How do bootable containers actually become a bootable system? What if you need to install a brand new bootc system? While bootc allows you to install a bootc container on an existing machine, it is more common to create a disk image—such as an ISO or qcow2 file—for deploying new systems. When the system boots, you will encounter a familiar experience that closely resembles ostree-based systems you may have used in the past.
To generate a disk image from a bootable container, you can use the image-builder tool, which is itself run inside a containerized environment. This tool offers a range of features, such as adding user accounts, configuring SSH keys, and specifying partition schemes. Among supported target image types are:
- QCOW2 (QEMU copy-on-write, virtual disk)
- Raw (.dmg)
- Installation ISO: Installation method, by using an USB drive, or Install-on-boot.
Bootc specifics:
We should note a few specifics related to Bootable Containers, that do not limit you, but might require you to adjust your default workflow: Bootc containers are shipped as ordinary containers, and are expected to be built using normal container build processes, but are primarily designed to run on booted on-prem/VM systems, so there are few interesting aspects:
- The filesystem on a bootc system is immutable. For this reason, all updates and configuration changes are captured in the Containerfile and applied during bootc upgrade.
- /etc is mutable, and changes to /etc files during upgrade are applied with a 3-way merge.
- bootc Containers are a complete operating system. For this reason they might not be optimal for quick iterative tasks; Application Containers are still a good tool for that.
- You shouldn’t “dnf upgrade” a bootc system. If you need updates, rebuild the container image, push it to the registry, update your deployed system using bootc tool.
- When preparing custom bootc images, use
bootc container lintas a final step to perform a sanity check.
Prerequisites:
- Oracle Linux 9 or Oracle Linux 10 system installed
- You can access Oracle Linux public repositories and have access to container registries to pull oraclelinux container images
To work with authenticated registries and bootc refer to the documentation on Container Registries and disconnected updates and Secrets - Default set of repositories enabled
- podman container management tool installed:
dnf install podman - To test booting converted bootc images into bootable qemu-kvm compatible images, install qemu-kvm:
dnf install qemu-kvm - Optionally access to a container registry to push your built bootc container images and test update workflow.
Root user:
Please note that the scripts referenced in this blog must be run with sudo or as the root user. This is necessary because image-builder requires root access to create install images, and podman stores container images separately for each user.
Building your baseline bootc image:
Bootc containers cannot be built from regular OCI Containers. Instead, you must start with a “bootc base image” that includes the tooling required to make a Container bootable. Oracle Linux ships the bootc-oraclelinux-config rpm package that provides the Containerfiles and scripts to allow you to easily build an Oracle Linux bootc base image. You can then deploy that base image, along with your customisations, in your organisation. You can own the whole lifecycle of bootc-powered deployment in an isolated environment, keeping in mind you still need access to repositories to actually build the system, you plan to use. So let’s build our baseline image:
dnf install bootc-oraclelinux-configscd /usr/share/bootc-oraclelinux-configs/ol9
or in case of Oracle Linux 10:
cd /usr/share/bootc-oraclelinux-configs/ol10
now run:
bash build_container_image.sh oraclelinux:9-bootc .
The build takes some time, but you will end up with baseline image being built.
Bingo(!). Generally this image can be used as is, but we encourage keeping the baseline image minimal, and instead use it to build your own customized containers.
Building your first custom bootc image:
Let’s prepare our first sample custom container. To make it real:
- Create a Containerfile:
FROM localhost/oraclelinux:9-bootc
RUN dnf -y install httpd && \
systemctl enable httpd && \
mv /var/www /usr/share/www && \
sed -ie 's,/var/www,/usr/share/www,' /etc/httpd/conf/httpd.conf
RUN rm /usr/share/httpd/noindex -rf
COPY index.html /usr/share/www/html
EXPOSE 80
- Create and index.html file:
touch index.html
and populate it with sample content:
<> html>
<body>
I am bootc
</body>
</html>
- Build the httpd image:
podman build -t bootc_httpd .
As you can see during the build process we are installing Apache HTTP server, and populating an index.html to display a dummy message. - Run the container:
podman run -d -p 80:80 bootc_httpd:latest - Verify that it’s running:
podman ps
You should see output similar to:
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
531312f16f21 localhost/bootc_httpd:latest /sbin/init 33 seconds ago Up 34 seconds 0.0.0.0:80->80/tcp flamboyant_galois
- Now try accessing httpd server by url:
http://<ip address of your server>:80
We will see:I am bootcBingo! our container works.
NOTE: you may need to adjust your firewalld rules or verify network accessibility for your system/container. - Now you can tag your container in the format, suitable for pushing to a container registry and push it. For example:
podman tag localhost/bootc_httpd:latest ocr.example.com/myname/bootc_httpd:latest
podman push ocr.example.com/myname/bootc_httpd:latest
Converting your first container image into bootable system:
To ensure a quick start for you we provide a set of scripts and configs that unlock the ability for you to convert your bootc container into a bootable image. As a sample we provide a script to convert your container to a qcow2 image, and also a “bootc installer” ISO image.
NOTE: as a part of the build process, a special container will be built, named oraclelinux-image-builder:latest. Conversion tooling will be executed in that container, accessing files on the host system when needed, as defined in the Containerfile. We encourage you to explore existing scripts and configs to “unlock” your imagination, modify and adapt those to your needs. The purpose of our configs and scripts is to “jumpstart” the bootc adoption process for you.
So, on previous steps we have ended up with a custom localhost/bootc_httpd:latest image. Let’s try converting that image to a qcow2 image, bootable in qemu.
cd /usr/share/bootc-oraclelinux-configs/image-builder- Have a look at
config.tomlfile, which is a basic config file for our bootable qcow2 image. config.toml is a config file designating the configuration of the qcow2 image that is to be built.
[[customizations.user]]
name = "opc"
password = "welcome1"
key = "BOOTC Image ... opc@oracle.com"
groups = ["wheel"]
[customizations.kernel]
append = "console=tty0 console=ttyS0,38400n8"
- In our case it is very simplistic, designating a user and password, adding that user to the wheel group, modify the kernel cmdline so that we have serial console, and providing a fake SSH
keyparameter.keyvalue is where you put your public SSH key.
NOTE: We strongly recommend to not use password at all, or use it only during development and debug stage, and instead use an SSH key in production. - Read the README.md file
- Place the image you want to convert into
bootc_container.conf, so that it looks similar to:
BOOTC_CONTAINER=localhost/bootc_httpd:latest
- Run
bash ol-image-builder.sh qcow2
Note: The conversion will take some time depending on configuration of your host system, but the build process provides detailed progress information. When conversion is done, you will see that the image is built and you will be dropped to the command line.
Images are placed into the output directory:
Image build successful: bootc-ol-9.7-qcow2-x86_64/bootc-ol-9.7-qcow2-x86_64.qcow2
- With qemu-kvm installed you can boot this converted image using a script
qemu-boot-bootc-qcow2.shthat we supply in/usr/share/bootc-oraclelinux-configs/image-builderIf you check that script it simply runs qemu-kvm using the image you supply:
/usr/libexec/qemu-kvm \
--enable-kvm \
--machine q35 \
-cpu host \
-smp 4 \
-m 8192 \
-nographic \
-snapshot "$1"
- In a while the system will boot and you can login with credentials you have suppled in config.toml. Since in this case we are testing the Apache HTTP server, if you want to actually access it, modify qemu-kvm arguments to include:
-net user,hostfwd=tcp::80-:80 \
-net nic \
- So that it looks like:
/usr/libexec/qemu-kvm \
--enable-kvm \
--machine q35 \
-cpu host \
-smp 4 \
-m 8192 \
-nographic \
-net user,hostfwd=tcp::80-:80 \
-net nic \
-snapshot "$1"
- Note: Adjust network settings and firewall configuration for to your actual environment.
Using Open Container Iniative “on the wire” updates
So far we have learned:
- How to build bootc containers ( Bootable Container containers 🙂 )
- How to convert those into a bootable qcow2 image and boot it in qemu-kvm
Now let’s test the “on the wire” update.
NOTE To test this functionality the bootc system needs network access to your container registry. Oracle Cloud Infrastructure provides a native Container Registry service that can be used for this purpose. Please ensure you can push and pull images from your registry of choice.
- Boot into the converted image in qemu-kvm
- Verify that you can login with user that you have setup in
config.tomlearlier - Confirm you can reach the network
- Check that
sudo bootc statuscommand correctly reports status of your image. If you have booted into our converted test httpd image, you should see output similar to the following:
bootc status
● Booted image: localhost/bootc_httpd:latest
Digest: sha256:1091ed792df7c0ab221b2c06d8c7477598ab6e92ea6c48615192e1d97e5690df (amd64)
Timestamp: 2026-01-15T13:40:08Z
- Prepare a new bootc based image with a desired set of changes. To have a better demonstration let’s build a custom image, not just an updated one: Let’s create a Containerfile on a system that is ready for bootc builds:
FROM localhost/oraclelinux-bootc:9-bootc
RUN dnf -y install mc
EXPOSE 80
- So pretty much we are taking a baseline system and then customize it to include Midnight Commander.
- Let’s build it:
Note You will also need to tag it in a format suitable for pushing to the registry.
podman build -t oraclelinux-test:9 .
- Let’s push it:
podman push ocr.example.com/myname/oraclelinux-test:9
- Now let’s consume this image as an update for our deployed system: bootc allows you to update an existing deployment by running
bootc upgrade, this will download and queue an updated container image to apply. In our case we are NOT updating from the same image as we have deployed from (as we have deployed from a local image), but instead we want to switch to a new bootc image. so we simply run:sudo bootc switch ocr.example.com/myname/oraclelinux-test:9
After that is done, we can checksudo bootc statusand see:
Staged image: ocr.example.com/myname/oraclelinux-test:9
Digest: sha256:978e8694078e939866549d6face1249b2302ca0b2ed12c45e3fb8b2840e1c636 (amd64)
Timestamp: 2026-01-15T14:40:08Z
● Booted image: localhost/bootc_httpd:latest
Digest: sha256:1091ed792df7c0ab221b2c06d8c7477598ab6e92ea6c48615192e1d97e5690df (amd64)
Timestamp: 2026-01-15T13:40:08Z
- Reboot and check that changes are in place. We can see that Midnight Commander is now available. httpd that was a part of the initial deployment is lost, since we were building a new image from a baseline one, which does not include httpd. And post reboot, to be sure, we can check
sudo bootc statusand see we are booted into a new image!:
bash-5.2# bootc status
● Booted image: ocr.example.com/myname/oraclelinux-test:9
Digest: sha256:978e8694078e939866549d6face1249b2302ca0b2ed12c45e3fb8b2840e1c636 (amd64)
Timestamp: 2026-01-15T14:40:08Z
Rollback image: localhost/bootc_httpd:latest
Digest: sha256:1091ed792df7c0ab221b2c06d8c7477598ab6e92ea6c48615192e1d97e5690df (amd64)
Timestamp: 2026-01-15T13:40:08Z
- As you can see you now also have a “Rollback image” available, which allows you to do a transactional rollback using
bootc rollback - Now you can continue issuing updated images, pushing them as
ocr.example.com/myname/oraclelinux-test:9and consuming updates on a regular basis, by runningsudo bootc upgradeand applying changes. - This simple workflow can easily be translated into a fully automated process of building an updated container image based on security fixes available, new versions of your application built, or any event. Ultimately you will also need either a manual interaction or some orchestration/system management tool to actually update the system.