This episode dissects building microVM images: creating a minimal rootfs with Alpine/musl, configuring the kernel with the fewest possible drivers, choosing the right boot params, and getting to know tooling such as firectl, firecracker-containerd, NixOS microvm.nix, and mkosi for build automation.

In episode 7 we locked down the microVM with the jailer. Now the question is: what's inside? Episode 8 covers every microVM's provisions — the image: a rootfs and kernel built specifically to boot fast, use little memory, and contain only what's needed.
Why is this episode important? Firecracker promises ~125 ms boot and < 5 MiB overhead — but that promise is only kept if the image cooperates. A bloated rootfs, a kernel loading hundreds of unused drivers, and a slow init will make a microVM boot in seconds and consume many times more memory. The image is half the performance.
Kernels and rootfs in the microVM world must follow different principles from a normal server distro:
With these principles, a microVM image becomes an artifact that can be built, tested, and reproduced deterministically — not a filesystem growing like a weed.
Alpine Linux is a popular choice for microVMs: it's small, uses musl libc, and boots from almost nothing. The plan: create an ext4 filesystem, bootstrap Alpine with apk, and install only the packages needed.
dd if=/dev/zero of=rootfs.ext4 bs=1M count=256
mkfs.ext4 rootfs.ext4
mkdir -p /mnt/fc-rootfs
sudo mount rootfs.ext4 /mnt/fc-rootfs
sudo mkdir -p /mnt/fc-rootfs/etc/apkAdd the Alpine repository and bootstrap:
sudo apk add --root /mnt/fc-rootfs --initdb --arch x86_64 alpine-baseInstall only what you need — for example, a shell and basic networking:
sudo apk add --root /mnt/fc-rootfs --no-cache \
alpine-baselayout busybox muslDon't install linux-firmware, don't install a whole desktop — every package adds boot time and memory. In the final stage, configure /etc/inittab so init runs with console=ttyS0, then unmount:
sudo umount /mnt/fc-rootfsTip
Use the --arch matching your host (x86_64 or aarch64) and choose an Alpine version that supports that architecture. If you use a ready-made rootfs from the Firecracker repository, make sure its contents match your needs — don't copy blindly without auditing.
The microVM kernel is built with a configuration that cuts everything unneeded. The main principles:
An example of the configuration direction (via make menuconfig or a config fragment):
CONFIG_VIRTIO=y
CONFIG_VIRTIO_MMIO=y
CONFIG_VIRTIO_BLK=y
CONFIG_VIRTIO_NET=y
CONFIG_VIRTIO_FS=y
CONFIG_VIRTIO_RNG=y
CONFIG_SERIAL_8250=y
CONFIG_SERIAL_8250_CONSOLE=y
CONFIG_EXT4_FS=y
CONFIG_NO_HZ_IDLE=yThen build:
make defconfig
make -j$(nproc) vmlinuxThe result is a vmlinux (uncompressed ELF) ready for Firecracker. The final size is usually under 10 MB.
Boot args determine how the kernel greets the virtual hardware:
{
"kernel_image_path": "/vmlinux.bin",
"boot_args": "console=ttyS0 reboot=k panic=1 pci=off iommu=off"
}console=ttyS0 — output to serial, readable by operators.reboot=k — reboot via KVM (instant, no power cycle).panic=1 — reboot 1 second after a kernel panic; an ephemeral microVM shouldn't wait for a human.pci=off — disable PCI scanning; speeds up boot when all devices are used via MMIO.iommu=off — for hosts without an IOMMU, avoids IOMMU initialization time.Boot params are a tuning instrument: without pci=off, the kernel wastes time scanning the PCI bus; with console=ttyS0, operators can read crashes. Choose according to your workload — don't copy blindly.
Building images by hand over and over isn't productive. The ecosystem provides tooling:
firectl — a CLI to launch Firecracker from an OCI container image; build once, run many (episode 12).firecracker-containerd — a containerd runtime that wraps container images into microVMs (episode 11).microvm.nix — declares an entire microVM image (kernel, rootfs, init) as a reproducible, testable Nix configuration.mkosi — builds complete OS images (systemd-based) from a declarative spec, including kernel and rootfs for microVMs.The common pattern of this tooling: declarative spec → image artifact. The kernel, rootfs, and init are defined once in configuration; the result is an immutable, hashed, distributed image. This is the foundation of the "build an image from a Dockerfile" flow we see in AWS Lambda MicroVMs (episode 18).
Warning
Hand-maintained images go stale fast: packages change, kernels change, and builds can't be reproduced. From the start, get into the habit of building images from declarative tooling (mkosi, microvm.nix, or a Dockerfile + firecracker-containerd) so image versions and contents can be audited.
vmlinuz can't be loaded; always use an ELF vmlinux./sbin/init exists and /etc/inittab is correct.console= not set: boot output disappears; debugging goes blind.The key takeaways:
console=ttyS0, panic=1, pci=off are real tuning instruments.In the next episode 9 we'll unlock the capability that makes Firecracker so beloved by serverless: Snapshot & Restore — freezing a running microVM with PUT /snapshot/create, bringing it back to life with PUT /snapshot/load, understanding the difference between memory and diff snapshots, and how this pattern delivers instant cold starts and suspend/resume.