Tuan-Anh Tran
How I moved beyond traditional patching to build a 0-CVE, immutable operating system for VM workloads using RHEL image mode.
January 21, 2026

Achieving a 0-CVE OS for VMs: The End of Traditional Patching

Posted on January 21, 2026  •  5 minutes  • 1058 words

Note: When I talk about 0-CVE, I mean zero known CVEs. It’s impossible to have true 0-CVE as new vulnerabilities are discovered daily, but I can aim for zero known vulnerabilities in my deployed images.

This post dives into the details of my previous talk, “From Container to Bare Metal: Redefining OS Build with bootc,” presented at Chainguard’s “In Containers We Trust” event.

From Containers to VMs

I started in 2022 with Chainguard’s Wolfi OS to make 0-CVE container images a reality. While that solved our container security story, it left a glaring gap: our VM workloads. I spent a lot of time looking for a way to bring that same security posture and developer experience to the virtual machine world.

Back in 2024, when I first learned about RHEL image mode (also known as RHEL bootable container) at Red Hat Summit, I knew this was the missing piece.

Building Confidence in Updates

My goals were straightforward, but the ultimate objective was to build user confidence. Without the confidence that an update can be safely applied and easily reverted, system owners simply won’t update.

Achieving this with traditional tools was surprisingly hard:

  1. 0 or near-zero CVEs: I needed a way to keep VMs as secure as our containers.
  2. Immutability: No more “snowflake” servers that drift over time.
  3. Easy Updates and Rollbacks: This is the mechanical key to building that user confidence. If you can’t rollback reliably, adoption will always fail.

If you work in a corporate environment, you know the fear that system owners often have: “If I run dnf update, will the application still work? And if it doesn’t, how do I go back?”

I didn’t want to keep using Ansible and Packer to spin up VMs, harden them, and snapshot them. I didn’t want to use Ansible to SSH in and run updates because manual rollbacks are a nightmare. I wanted to deliver the update as a single, immutable unit—just like a container image.

RHEL image mode checked all my boxes

RHEL image mode checked all my boxes. It allows you to build a bootable disk image using the same Dockerfile syntax I use for containers.

Here is a simplified example of what a bootable container Dockerfile might look like:

FROM registry.redhat.io/rhel10/rhel-bootc@sha256:cdc39ac8e531ce2b826e467a75d81e9f0fd7a30326c8c8d02fc5ea8bb224dc6e

# Basic hardening and package management
RUN ...

# Add your application or configuration
COPY my-app.service /etc/systemd/system/
RUN systemctl enable my-app.service

Once built, I can push this update to a standard container registry. From there, bootc-image-builder can take that same image and output it as an AWS AMI, a VMware VMDK, or even a raw disk image.

Why this works:

I built my setup around a simple model

I built my setup around a simple but powerful model. I have a repository dedicated to building and hardening a base image, stripping out unnecessary packages to minimize the attack surface.

The repository structure looks something like this:

.
├── rhel9/
│   ├── Dockerfile
│   └── baselayout/
├── rhel10/
│   ├── Dockerfile
│   └── baselayout/
├── python-3.14/
│   ├── Dockerfile
│   └── baselayout/
├── jre/
│   ├── Dockerfile
│   └── baselayout/
└── README.md

Each folder represents a specific bootable image. The baselayout directory contains configuration files, systemd units, and scripts that are copied into the image during the build.

To keep things fresh, I use a tool I call digestbot. Just like Renovatebot or Dependabot, it watches for updates to the official Red Hat base images. When a new version is released, it automatically:

  1. Triggers a rebuild of my hardened base image.
  2. Triggers rebuilds of all application images that extend from it.
  3. Runs automated tests against the new images: compliance checks, vulnerability scan, …

Every day at 9 AM, like clockwork, the scan is triggered and the entire process repeats.

The usage workflow is simple

For the end-user (the application owner), the workflow is incredibly simple. I provide a script that checks for available updates and, if a new version is found, prints the exact bootc update command they need to run. When they are ready to deploy, they use two main commands:

# Pull the latest image and prepare the update
bootc update <image>

# Switch to the new version (may requires a reboot)
bootc switch <image>

If something goes wrong after the reboot, rolling back is just as easy. This confidence is what finally broke the adoption barrier in our organization.

I plan to go a step further for teams that are fully confident in the process by introducing an automated update mechanism. System owners can opt into this simply by tagging their EC2 instances. For example, by setting tags like example.com/prefer-maintence-window and example.com/soft-apply: yes, the update is applied automatically during their specified window. This takes the “human” out of the loop for standard security patches while allowing them to remain in control via simple AWS tags.

But before I can do that, I need to find a way to standardize health checks for applications. Without a reliable way to verify that an application is healthy after an update, automated rollouts carry too much risk.

RHEL image mode is definitely worth exploring

If you’re wrestling with VM patching and security, RHEL image mode is definitely worth a look. It bridges the gap between the flexibility of VMs and the security of modern container workflows. It might not be the right fit for every use case, but for workloads that can be containerized or run in an immutable format, it solves a lot of problems I’ve been wrestling with for years.

Follow me

Here's where I hang out in social media