alwaysInBeta Stable software is for the weak

Trying to work with bootc - initial attempts

2025-03-27

What is it

(This is a technical post; if you’re wondering what an immutable OS is or why you would use one, don’t start here. There’s surprisingly little in the way of general-purpose overviews, but maybe start by reading this post, which covers the big picture decently well before trying to sell you on their particular take.)

Having tried several different takes on immutable OSs, one of the latest things to come up is bootc, the major selling point of which is that you can build your OS image from by starting from a Containerfile/Dockerfile, building that into an OCI image, and then throw that image onto the disk and make it possible to actually boot it as the real host OS without a container runtime or anything. This sounds really cool, not least because I would absolutely love to make the process of making your own distro/respin as simple as “fork this spec file, run a build command, and boot the resulting disk image”. I had previously looked at it, but turned away after not being able to find any docs that would tell me how to actually use it for anything but a Red Hat family OS (Fedora, CentOS Stream, RHEL). More recently, however, I got pulled back in and found accounts of people managing to build Arch Linux systems on it, implying that what I want to do should be possible. As such, I’m going to write up how to use bootc at all, and then attempt to document my efforts to use it on increasingly non-RH systems.

What am I going to do

My rough plan is:

Prereqs

The tool we’ll be using for all of these initial exercises is bootc-image-builder, the tool that actually turns OCI images into qcow2 images (or whatever you choose). AFAICT, this is only meant to be run through podman. I don’t claim to understand why that’s the only way to run it (surely it should be able to run on the host?), but being able to run it in podman is handy.

I am running these experiments on an openSUSE MicroOS (Kalpa) system, but I expect any machine running podman is fine.

I will also be testing the images in qemu, which in my particular case happens to be running inside distrobox in an Alpine Linux container, but that’s an irrelevant implementation detail; you need something that can run the bootable image we produce, but how you do that is up to you.

First: CentOS Stream unmodified

AKA, don’t modify or deviate at all, just try to make sure you can use this thing in any capacity.

This follows along from the examples in the project readme.

Create a new directory and cd into it.

mkdir -p bootc-testing/centosstream9-trivial
cd bootc-testing/centosstream9-trivial

Write a config file for the system to create a non-root user. There are more config options that we’re not using for this simple case.

cat > config.toml <<EOF
[[customizations.user]]
name = "test"
password = "test"
groups = ["wheel"]
EOF

Download the OCI image that will be used.

sudo podman pull quay.io/centos-bootc/centos-bootc:stream9

Create an output directory (why does podman not create it for me?).

mkdir output

And now, run the actual build!

sudo podman run \
    --rm \
    -it \
    --privileged \
    --pull=newer \
    --security-opt label=type:unconfined_t \
    -v ./config.toml:/config.toml:ro \
    -v ./output:/output \
    -v /var/lib/containers/storage:/var/lib/containers/storage \
    quay.io/centos-bootc/bootc-image-builder:latest \
    --type qcow2 \
    --use-librepo=True \
    quay.io/centos-bootc/centos-bootc:stream9

This will show a nice series of progress bars with some messages to tell you what it’s doing. On my (mid-range?) box it took 3 minutes.

Now fix ownership and boot the resulting image:

sudo chown -R $(whoami) output/
qemu-system-x86_64 \
    -M accel=kvm \
    -cpu host \
    -smp 2 \
    -m 4096 \
    -serial stdio \
    -snapshot output/qcow2/disk.qcow2

Be aware that it will take a minute to get any output on the serial console (which is connected to your terminal).

I have modified the example command from the readme to drop -bios, which I suspect is a Fedora-ism.

And because I had to look it up:

If you use the option -snapshot, all disk images are considered as read only. When sectors in written, they are written in a temporary file created in /tmp. You can however force the write back to the raw disk images by using the commit monitor command (or C-a s in the serial console).

Once the system has booted, log in with the above credentials (test/test), poke around if desired, and then run sudo poweroff. Notice that it prompts you for a sudo password; this will become important in the next section.

Second: CentOS Stream barely modified

Now that we’ve proven that we can convert container images into bootable qcow2s, we’ll make our own container image, albeit basically the same as upstream. In fact, we will use the example given in the bootc-image-builder readme, in the “Accessing the system” section.

Create a new directory and cd into it.

mkdir -p bootc-testing/centosstream9-modified
cd bootc-testing/centosstream9-modified

Write a config file for the system to create a non-root user. (This is the same as before.)

cat > config.toml <<EOF
[[customizations.user]]
name = "test"
password = "test"
groups = ["wheel"]
EOF

Describe and build our new image.

cat > Containerfile <<EOF
FROM quay.io/centos-bootc/centos-bootc:stream9
ADD wheel-passwordless-sudo /etc/sudoers.d/wheel-passwordless-sudo
EOF

cat > wheel-passwordless-sudo <<EOF
%wheel ALL=(ALL) NOPASSWD: ALL
EOF

sudo podman build -t cs9-custom .

Notice that this says

Successfully tagged localhost/cs9-custom:latest

which is different than docker, if you’re used to that. (A reasonable person might suggest that it’s a good practice to qualify local builds as localhost/whatever in docker, too, but it doesn’t default to that.)

Create an output directory.

mkdir output

And now, run the actual build.

sudo podman run \
    --rm \
    -it \
    --privileged \
    --pull=newer \
    --security-opt label=type:unconfined_t \
    -v ./config.toml:/config.toml:ro \
    -v ./output:/output \
    -v /var/lib/containers/storage:/var/lib/containers/storage \
    quay.io/centos-bootc/bootc-image-builder:latest \
    --type qcow2 \
    --use-librepo=True \
    localhost/cs9-custom

Where the only change is that we ask it to build from localhost/cs9-custom. This will again take a few minutes to build and give status updates along the way.

Now fix ownership and boot the resulting image:

sudo chown -R $(whoami) output/
qemu-system-x86_64 \
    -M accel=kvm \
    -cpu host \
    -smp 2 \
    -m 4096 \
    -serial stdio \
    -snapshot output/qcow2/disk.qcow2

Once the system has booted, log in with the above credentials (test/test), poke around if desired, and then run sudo poweroff. This time, notice that it does not prompt you for a sudo password; this shows that our change worked.