Context & Problems Solved
Operating server clusters over time frequently leads to configuration drift. An Ansible playbook might succeed on one node but fail on another due to subtle differences in the underlying base packages. The Image-based OS model fundamentally resolves this issue by freezing the entire operating system into a single immutable artifact.
Previously, tools like Packer or Image Builder produced rather heavyweight images. bootc (Bootable Containers) brings the familiar container workflow down to the operating system level. You write a Containerfile, build it with podman build, and push it to GHCR or Quay.io. The server pulls that image and loads it directly into the kernel to boot bare-metal hardware or virtual machines.
The operating system now functions as an immutable image with clear semantic tags (such as v1.0.4). If an update causes a kernel issue, you can roll back to the previous working state with a single reboot.
Preparing the Environment
You need a Fedora Workstation or Server machine with Podman pre-installed to follow along:
1. Install bootc
# Install the bootc CLI
sudo dnf install -y podman bootc
# Verify the version
bootc --version
2. Pull the Image Builder
We use bootc-image-builder to convert the container image into a virtual disk (.qcow2, .raw, or .ami):
# Pull the official image builder
podman pull quay.io/centos-bootc/bootc-image-builder:latest
Detailed Configuration
1. Write a Containerfile to Define the OS
Create a project directory and a Containerfile. We will use the Fedora bootc base image, install administration tools, configure a user, and enable SSH:
FROM quay.io/fedora/fedora-bootc:40
# Install required packages in a single layer
RUN dnf -y install \
tmux \
htop \
neovim \
curl \
git \
openssh-server \
&& dnf clean all
# Enable SSH service on boot
RUN systemctl enable sshd
# Create an admin user and grant sudo privileges
RUN useradd -m -G wheel -s /bin/bash sysadmin && \
echo 'sysadmin:AdminSecret123!' | chpasswd && \
echo "%wheel ALL=(ALL) NOPASSWD: ALL" > /etc/sudoers.d/wheel-nopasswd
# Configure SSH Public Key for the user
RUN mkdir -p /home/sysadmin/.ssh && \
chmod 700 /home/sysadmin/.ssh && \
echo "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIG... user@workstation" > /home/sysadmin/.ssh/authorized_keys && \
chmod 600 /home/sysadmin/.ssh/authorized_keys && \
chown -R sysadmin:sysadmin /home/sysadmin/.ssh
2. Build the OS Container Image
The build process is identical to building a standard web application container:
podman build -t quay.io/myrepo/fedora-custom-os:v1.0 .
3. Export QCOW2 Virtual Disk for KVM / Proxmox
Run the container builder to generate a virtual disk file from the newly built image:
mkdir -p ./output
sudo podman run \
--rm \
--privileged \
--security-opt label=type:unconfined_t \
-v ./output:/output \
-v /var/lib/containers/storage:/var/lib/containers/storage \
quay.io/centos-bootc/bootc-image-builder:latest \
--type qcow2 \
--local \
quay.io/myrepo/fedora-custom-os:v1.0
The disk export process takes about 2-3 minutes. The output produces the file ./output/qcow2/disk.qcow2 (around 2.2GB – 2.5GB), ready to be imported into Proxmox or virt-manager to spin up a VM.
Best Practices for Image Design
- Partitioning scheme: The
/usrpartition is mounted read-only. Only/etc(for configuration) and/var(for logs, databases, container storage) are writable. - Image size optimization: Group all
dnf installpackages together and always includednf clean allwithin the same layer to prevent unnecessary image bloat. - Separate build stages: Use multi-stage builds to compile Go/Rust binaries in the initial stage, then copy the clean binary artifacts into the final bootc base image.
Operations & Monitoring
1. Check Deployment Status
SSH into the bootc server and run the status check command:
sudo bootc status
The output will display the active image (staged/booted), the SHA256 digest, and the linked remote registry.
2. Upgrade the Operating System (In-place Upgrade)
After modifying the Containerfile and pushing tag v1.1 to the registry, you can update the OS with just two commands:
# Pull the new build into the staging area
sudo bootc upgrade
# Reboot to apply the new OS
sudo systemctl reboot
If you need to switch to a different base image (e.g., from the standard image to one with pre-installed Nvidia drivers):
sudo bootc switch quay.io/myrepo/fedora-nvidia-os:latest
sudo systemctl reboot
3. Rollback on Failure
If an update triggers a kernel panic or crashes networking services, revert immediately:
# Roll back to the previous deployment
sudo bootc rollback
sudo systemctl reboot
If the server loses network connectivity, you can select the previous kernel entry directly from the GRUB boot menu.
4. Automated Update Checks
Enable the built-in systemd timer to automatically check for new image updates on a schedule:
sudo systemctl enable --now bootc-fetch.timer
sudo systemctl list-timers | grep bootc
The timer silently pulls new layers in the background. The system will apply the update on the next reboot without disrupting running services.

