Repurposing a bootc host
In addition to simplifying updates and providing native rollback, operating RHEL in image mode also makes it simple to quickly change the purpose of a running system. This means experimenting with new versions of application components or testing OS updates is as simple as applying any other image change.
In this lab we’ll explore this feature as well as expand on the idea of standardized builds and derived images.
Building from the standard base
Instead of just running a webserver, what if we needed to run WordPress instances, and our developers need those to be containerized? Normally, that would start the hunt for available infrastructure, an examination of the software needed to support the containers, changes to automation, deploying new machines to support the testing, and a lot of back and forth between the different teams.
With image mode, a few new options are available. The developers can take a standard build that has podman
available, add their container configurations, and pick an existing host to run the image. If they have problems or if they need to change the host back to it’s previous role, that’s as simple as a rollback.
Let’s explore how that can work by creating a new Containerfile with the following contents.
nano Containerfile.wp
FROM {registry_hostname}/httpd
RUN dnf -y install lynx
ADD wordpress-quadlet/etc/ /etc
ADD wordpress-quadlet/usr/ /usr
For an 'advanced` configuration, this doesn’t have a lot in it, what’s happening?
First up, the FROM line references the image you’ve been building and updating during this lab. This means that every bit of customization and software you’ve installed previously will be available on the host built from this new definition. This style of derived images is something very powerful for collaborating while keeping teams focused on their needs. Different teams can work directly from images built by and certified by others, rather than starting from scratch or trying to integrate and apply controls at the end of a long build process.
Remember the ADD
directive pulls full directories into the image at build time. So most of the work here is
somehow being done in /usr
or /etc
, let’s see what’s in there.
ls -lahR wordpress-quadlet/etc/ wordpress-quadlet/usr/
The "hidden" part is the use of quadlets to run containers. A full description of quadlets is outside the scope of this lab, but in short, a quadlet is a way to run a container (or group of containers in this case) as a systemd service.
In wordpress-quadlet/etc/
we have a configuration file for the caddy server, a Golang HTTPS server acting as a proxy, and a file of environment variables to be passed to the quadlet.
In wordpress-quadlet/usr/share/containers/systemd/
we have all of the files that define the quadlet. The .container
file defines each of the containers for systemd to run. These are typical systemd unit files, aside from the [Container]
block which is unique to quadlets.
Feel free to explore these files and directories before moving on.
Build and push the image
When we build this image, we will use a new name to denote it’s new purpose. Your naming and tagging conventions should aim to convey information to the people who need them as much as providing hooks to automate and control visibility to hosts.
podman build --file Containerfile.wp --tag {registry_hostname}/caddy-wp
And of course push it to the local registry:
podman push {registry_hostname}/caddy-wp
Notice that even though we used a new tag for this image, the push still used cached layers. This is an advantage of stacking images in a standard build design.
You can now login to the virtual machine:
ssh core@qcow-vm
Switch and test the image
After the new container image has been pushed to the local registry, you can switch
the bootc image to the WordPress one. This bootc
command is how we change what image to follow for updates. From here on, any changes made to the original httpd
image would not show up as an available update, only changes to the new caddy-wp
image.
sudo bootc switch {registry_hostname}/caddy-wp
From a status perspective, a switch looks the same as an update. There’s a new deployment that’s been staged and prepared for the next boot, but the image reference is completely different.
sudo bootc status
Staged image: node.prk8w.gcp.redhatworkshops.io/caddy-wp Digest: sha256:f139ae15566be551658c239eaadbdbc9ff3db72338cf41a7c85e5574903dad86 Version: 9.6 (2025-05-15 14:16:09.529889448 UTC) ● Booted image: node.prk8w.gcp.redhatworkshops.io/httpd:10.0 Digest: sha256:5411c8f3d00f20ca500952b61a25d482a61e33d00df146f01c9aff28a2944400 Version: 10.0 (2025-05-15 13:57:13.220163203 UTC) Rollback image: node.prk8w.gcp.redhatworkshops.io/httpd Digest: sha256:daeb8ddb6ad93a842488b216d4816054db1f70b8f811adbb906e5d6b5634e65c Version: 9.6 (2025-05-15 13:37:00.922495571 UTC)
As usual, after the command is done you need to reboot the virtual machine
for the changes to take effect. Before doing that, please make sure you are logged in to the
virtual machine and not the hypervisor (the prompt should look like [core@qcow-vm ~]$
):
sudo systemctl reboot
After a short while, you can log back in to the virtual machine:
ssh core@qcow-vm
Troubleshooting derived builds
Check on the status of our newly created quadlet by checking the caddy proxy server and what it inherited from the standard build.
sudo systemctl status caddy.service --no-pager
journalctl -t caddy
Jul 24 14:40:30 qcow-vm systemd[1]: caddy.service: Failed with result 'exit-code'. Jul 24 14:40:30 qcow-vm systemd[1]: Failed to start Caddy Quadlet. Jul 24 14:40:25 qcow-vm caddy[1347]: Error: cannot listen on the TCP port: listen tcp4 :80: Jul 24 14:40:27 qcow-vm caddy[1780]: Error: cannot listen on the TCP port: listen tcp4 :80:
We weren’t prompted for our sudo password, but it looks like our new caddy server couldn’t bind to port 80 when it tried to start. What’s going on? The answer to both lies in the image we built from.
If we check the status of Apache, we can see that it is indeed running and listening on port 80.
systemctl status httpd.service --no-pager
If you look at the original Containerfile, you’ll recall we set Apache to start at boot:
RUN systemctl enable httpd.service
Since local changes to /etc are kept by bootc
when changing images, httpd stayed enabled on
this new host as well. Let’s disable it and restart caddy.
sudo systemctl disable --now httpd.service
sudo systemctl restart caddy.service
sudo systemctl status caddy.service
Removed "/etc/systemd/system/multi-user.target.wants/httpd.service". ● caddy.service - Caddy Quadlet Loaded: loaded (/usr/share/containers/systemd/caddy.container; generated) Active: active (running) since Wed 2024-07-24 14:42:21 UTC; 6s ago
It looks like caddy started, let’s check to see that it’s passing requests to the WordPress container in the quadlet. Curl will dump a mess of HTML and we don’t have a GUI, but that’s why we installed the Lynx browser.
lynx localhost
You should see the WordPress configuration dialog box. You can hit Q
(Shift + q) to quit lynx and then log out of the VM.
logout
Disable the service and rebuild with new tag?
That solution is fine for this host, but how to we fix it in the image? The answer depends on the goal and how container layers operate.
The simplest solution would be to stop the service from starting. You may find there are other services in the base image you may want to disable in certain downstream images as well. One way to stop these sorts of services from activation, especially those defined in /usr/lib/systemd/system
, is with masking.
Let’s add a heredoc
to our Containerfile to tell systemd to mask the httpd service and the bootc update timer.
nano Containerfile.wp
FROM {registry_hostname}/httpd
RUN dnf -y install lynx
ADD wordpress-quadlet/etc/ /etc
ADD wordpress-quadlet/usr/ /usr
RUN <<EORUN
set -euxo pipefail
systemctl mask httpd.service
systemctl mask bootc-fetch-apply-updates.timer
EORUN
This means these services won’t even be able to be started manually.
podman build --file Containerfile.wp --tag {registry_hostname}/caddy-wp:V2
And of course push it to the local registry:
podman push {registry_hostname}/caddy-wp:V2
The name of the image has stayed the same, but we’ve now added the V2
to add some semantic versioning. Tags are part of how bootc
keeps track of images, which is important when it comes to updates. Since this is a new tag, we would need to switch
to it in order to use it, just like we did the first time. This might be fine, especially since we’ve turned off auto-updates.
We can check this by having bootc
look for an update. Notice the new V2
image isn’t seen.
ssh core@qcow-vm sudo bootc update
No changes in node.545jj.gcp.redhatworkshops.io/caddy-wp => sha256:c15b09203ea36a342135cc2d1c061ea96c0b61f4e5c46fd38bc8afe3f6c787a0 No update available.
Remember from the OS base image discussion that an image can have multiple tags associated with it, and bootc
can track an single tag for an image. To make this new image appear as be an update, then we can add a tag to this new image that matches what bootc
is currently tracking.
Since we haven’t been adding tags to any of our previous builds, they all have the default latest tag automatically applied.
podman images {registry_hostname}/caddy-wp
You can see both of our caddy images, and the tags associated with them. To make V2
appear as an available update, add the latest
tag to that image. The first image is the one to operate on, and the second is the complete target name you want to apply. This can be used in a lot of ways, for example to build all images locally without any registry, organization, and tag info and add that only to specific builds that are ready to be pushed to a registry.
podman image tag {registry_hostname}/caddy-wp:V2 {registry_hostname}/caddy-wp:latest
This changed the tag locally, so we need to push the newly tagged image to the registry to pick up the change there. You’ll notice all of the layers are skipped since this is really just a metadata change.
podman push {registry_hostname}/caddy-wp:latest
Logging back into the VM, you should see an update available for the caddy-wp
image with the one layer that has our change.
ssh core@qcow-vm
sudo bootc update
Feel free to apply the update and test the changes.
Before proceeding to the next exercise, make sure you have logged out of the virtual machine:
logout
Core principles
Easy updates, rollbacks, and image switching are part of the core improvements to the operation of image mode systems. Layering is an important part of the design of standard builds and can have some downstream effects as well. Just like stacking configuration management, thinking through the idea of layered builds can be powerful.
In this lab we’ve covered the very basics of image mode to get you started in your exploration of what this can do for you and your environment. There’s a lot we haven’t covered, like compliance, managing kernel arguments, integrating with other tools like Ansible or Satellite, and much more. Image mode is just leaving tech preview, so there’s a full roadmap of improvements to come.
We’ve got other ways to learn more, including documentation, example snippets, other exercises, and more.
In the next modules, we’ll add a little more color to ways image mode can be used.