Build a KVM-Ready Container Image from Scratch (2026 Guide)
KVM Virtual Machines on Podman #1: Build a KVM-Ready Container Image
Quick one-liner: Learn how to build a custom Podman container image with KVM/QEMU installed — the first step to running hardware-accelerated virtual machines inside containers.
Why This Matters
You've probably heard that containers and virtual machines are different things. Containers share the host kernel. VMs have their own kernel. They're opposites, right?
Well, here's the thing: sometimes you need both.
Maybe you need to test software on a different architecture. Or run a legacy OS that won't work in a container. Or isolate something even more securely than containers provide.
That's where KVM and QEMU come in. QEMU is a free, open-source emulator that can run virtual machines. KVM (Kernel-based Virtual Machine) is the Linux kernel feature that gives QEMU direct access to your CPU's hardware virtualization extensions (Intel VT-x or AMD-V). And yes — you can run them inside a container.
But here's the catch: The official QEMU images are built for specific use cases. If you want full control over what's installed and how it's configured, you need to build your own.
This guide walks you through building a custom Podman container image with QEMU and KVM support installed from scratch. No black boxes. No mystery dependencies. Just you, a Containerfile, and a working KVM setup.
By the end, you'll have:
- A custom Containerfile tailored for KVM/QEMU
- A working Podman image with QEMU installed
- Understanding of what each layer does
- A foundation to build on in future posts (next: enable KVM acceleration!)
Prerequisites
- Podman installed (rootless mode is the default — see your distro's Podman package)
- 5-10 minutes to build the image
- Terminal access to your Podman host
- Basic Containerfile knowledge (FROM, RUN, CMD instructions)
Step 1: Create Your Project Directory
First, let's set up a clean workspace.
$ mkdir -p ~/qemu-container
$ cd ~/qemu-container
You're going to build everything in this directory. When you're done, you can delete it or keep it for reference.
Step 2: Write the Containerfile
Create a file named Containerfile (no extension) in your project directory:
$ nano Containerfile
Here's what goes in it:
# QEMU Container Image — Base Setup
# Build: podman build -t qemu-base .
# Run: podman run --rm -it qemu-base
FROM ubuntu:24.04
LABEL maintainer="Your Name <your.email@example.com>"
LABEL description="QEMU emulator in a Podman container"
LABEL version="1.0"
# Prevent interactive prompts during package installation
ENV DEBIAN_FRONTEND=noninteractive
# Update package lists and install QEMU
RUN apt-get update && \
apt-get install -y --no-install-recommends \
qemu-system-x86 \
qemu-utils \
qemu-system-common \
libvirt-daemon-system \
libvirt-clients \
bridge-utils \
virt-manager \
&& apt-get clean \
&& rm -rf /var/lib/apt/lists/*
# Set working directory for VM files
WORKDIR /vms
# Default command — show QEMU version
CMD ["qemu-system-x86_64", "--version"]
Let me break down what each section does:
| Line | What It Does |
|---|---|
FROM ubuntu:24.04 | Start from Ubuntu 24.04 LTS — stable, well-documented, good QEMU support |
ENV DEBIAN_FRONTEND=noninteractive | Prevents package installation from hanging on configuration prompts |
RUN apt-get update && apt-get install -y | Updates package lists and installs QEMU packages |
--no-install-recommends | Skip optional packages — keeps the image smaller |
qemu-system-x86 | The main QEMU emulator for x86_64 machines |
qemu-utils | Utilities like qemu-img for managing disk images |
qemu-system-common | Common files shared by QEMU system emulators |
libvirt-daemon-system | Libvirt daemon for managing virtualization |
libvirt-clients | Client tools like virsh to interact with libvirt |
bridge-utils | Network bridge utilities for VM networking |
virt-manager | Virtual Machine Manager GUI (optional, useful for testing) |
apt-get clean && rm -rf /var/lib/apt/lists/* | Cleans up package cache — reduces image size |
WORKDIR /vms | Set default working directory for VM files |
CMD ["qemu-system-x86_64", "--version"] | Shows QEMU version when container starts (useful for testing) |
Why Ubuntu? You could use Alpine, Debian, or Fedora. But Ubuntu has the best documentation, largest community, and most stable QEMU packages. For a learning setup, it's the right choice.
Save the file and exit.
Step 3: Build the Image
Now build the image:
$ podman build -t qemu-base .
You should see output like:
STEP 1/10: FROM ubuntu:24.04
STEP 2/10: LABEL maintainer="Your Name <your.email@example.com>"
...
STEP 10/10: CMD ["qemu-system-x86_64", "--version"]
COMMIT qemu-base
--> a1b2c3d4e5f6
Successfully built qemu-base
The build downloads the base Ubuntu image, installs QEMU and all dependencies, then commits the result as qemu-base.
First build tip: The first time you build, it'll take a few minutes to download packages. Subsequent builds are faster because Podman caches layers.
Step 4: Test the Image
Let's verify QEMU is actually installed and working:
$ podman run --rm qemu-base
You should see QEMU's version information:
QEMU emulator version 8.2.2 (Debian 1:8.2.2+ds-0ubuntu1.13)
Copyright (c) 2003-2023 Fabrice Bellard and the QEMU Project developers
Success! QEMU is installed and working inside the container.
But wait — that's just the version check. Let's actually run QEMU interactively:
$ podman run --rm -it qemu-base /bin/bash
You're now inside the container. Try running QEMU directly:
root@container-id:/vms# qemu-system-x86_64 --version
Same version output. Good.
Now let's try something more interesting — list CPU models:
root@container-id:/vms# qemu-system-x86_64 -cpu help
This lists all CPU models QEMU can emulate. You should see a long list including qemu64, host, Nehalem, Haswell, and many more.
Exit the container:
root@container-id:/vms# exit
Step 5: Check Image Size
Let's see how big this image is:
$ podman images qemu-base
You should see something like:
REPOSITORY TAG IMAGE ID CREATED SIZE
localhost/qemu-base latest 568a6950c2ea 5 minutes ago 439 MB
439 MB — pretty reasonable for a full QEMU setup with GUI tools.
Want it smaller? Remove virt-manager and libvirt packages if you only need command-line QEMU. That shaves off ~100 MB. But for learning, the full setup is worth it.
Step 6: Tag and Organize
Let's give this image a better tag for future use:
$ podman tag qemu-base qemu:base
Now you can refer to it as qemu:base instead of qemu-base.
List your images:
$ podman images qemu
You should see both tags pointing to the same image ID:
REPOSITORY TAG IMAGE ID CREATED SIZE
qemu base a1b2c3d4e5f6 3 minutes ago 439 MB
qemu-base latest a1b2c3d4e5f6 3 minutes ago 439 MB
What You've Built
You now have a working QEMU container image with:
- ✅ QEMU system emulator (x86_64)
- ✅ Disk image utilities (
qemu-img) - ✅ Libvirt management tools
- ✅ Network bridge support
- ✅ Clean, documented Containerfile
But here's the thing: Right now, this is just an image. You can run QEMU commands, but you can't actually boot a VM yet.
Why? Because you don't have a disk image to boot.
What's Next?
You've got QEMU installed in a container. But if you try to boot a VM right now, it'll be painfully slow — like, 10 minutes to boot an OS that normally boots in 30 seconds.
Why? Because you're using pure software emulation. Every CPU instruction is translated by QEMU instead of running directly on your hardware.
Next time: We'll enable KVM acceleration — Intel VT-x or AMD-V hardware virtualization — and speed up VM boot times by 10-20x.
But there's a catch: KVM requires special device access from inside the container. And that's where things get interesting with Podman.
This guide is Part 1 of the KVM Virtual Machines on Podman series. Each post builds on the last, adding one capability at a time.
Coming up in Part 2: Enable KVM Acceleration: 10x Faster VMs in Rootless Podman
If you're following along and want to see something specific, drop a comment or reach out.
Comments
Post a Comment