Skillversitas

Creating a VM Image

MicroVM cannot boot from a block device via BIOS/UEFI because of the virtio-mmio limitations. We need to create a root filesystem image that is booted directly using a Linux kernel.

Preparation

We need:
1. Linux kernel (vmlinuz) - From the host or a custom build
2. Root filesystem image - An ext4 filesystem inside a file
3. Init system (optional) - BusyBox or systemd

Creating a Root Filesystem with Debootstrap

Debootstrap creates a minimal Debian/Ubuntu rootfs without a full installation:

# Install debootstrap
sudo apt install debootstrap

# Create a 2GB file image
fallocate -l 2G rootfs.img

# Format as ext4
mkfs.ext4 -L microvm-root rootfs.img

# Mount the image
sudo mkdir -p /mnt/microvm
sudo mount -o loop rootfs.img /mnt/microvm

# Install minimal Debian
sudo debootstrap --variant=minbase bookworm /mnt/microvm http://deb.debian.org/debian

# Cleanup
sudo umount /mnt/microvm

Creating an Image with Alpine (Lighter)

Alpine Linux is very well suited to MicroVM because of its small size:

# Download the Alpine mini rootfs
wget https://dl-cdn.alpinelinux.org/alpine/v3.19/releases/x86_64/alpine-minirootfs-3.19.1-x86_64.tar.gz

# Create the image
fallocate -l 512M rootfs.img
mkfs.ext4 -L alpine-root rootfs.img

# Mount and extract
sudo mkdir -p /mnt/microvm
sudo mount -o loop rootfs.img /mnt/microvm
sudo tar -xzf alpine-minirootfs-3.19.1-x86_64.tar.gz -C /mnt/microvm

# Basic configuration
sudo chroot /mnt/microvm /bin/sh -c '
echo "microvm" > /etc/hostname
echo "nameserver 8.8.8.8" > /etc/resolv.conf
echo "ttyS0::respawn:/sbin/getty -L ttyS0 115200 vt100" >> /etc/inittab
'

# Setup fstab
echo "/dev/vda / ext4 defaults 0 1" | sudo tee /mnt/microvm/etc/fstab

# Cleanup
sudo umount /mnt/microvm

Creating an Image from the Host Kernel

Use the host kernel to avoid a manual compile:

# Find the available kernels
ls /boot/vmlinuz*

# Copy the kernel to the working directory
cp /boot/vmlinuz-$(uname -r) vmlinuz

# Copy the initramfs (optional, if needed)
cp /boot/initrd.img-$(uname -r) initrd.img

Building a Custom MicroVM Kernel (Optional)

For optimal performance, build a kernel with a minimal configuration:

# Download kernel source
wget https://cdn.kernel.org/pub/linux/kernel/v6.x/linux-6.7.tar.xz
tar xf linux-6.7.tar.xz
cd linux-6.7

# Start from defconfig
make defconfig

# Enable only what is needed
scripts/config --enable CONFIG_VIRTIO
scripts/config --enable CONFIG_VIRTIO_MMIO
scripts/config --enable CONFIG_VIRTIO_BLK
scripts/config --enable CONFIG_VIRTIO_NET
scripts/config --enable CONFIG_EXT4_FS
scripts/config --enable CONFIG_SERIAL_8250
scripts/config --enable CONFIG_SERIAL_8250_CONSOLE
scripts/config --disable CONFIG_PCI
scripts/config --disable CONFIG_ACPI
scripts/config --disable CONFIG_USB
scripts/config --disable CONFIG_SOUND
scripts/config --disable CONFIG_DRM
scripts/config --disable CONFIG_WIRELESS
scripts/config --disable CONFIG_BLUETOOTH
scripts/config --disable CONFIG_SECURITY

# Build
make -j$(nproc)

# Copy the result
cp arch/x86/boot/bzImage ../vmlinuz-microvm

Configuring the Network Inside the Image

# Mount the image
sudo mount -o loop rootfs.img /mnt/microvm

# Configure the network interface
sudo tee /mnt/microvm/etc/network/interfaces << 'EOF'
auto lo
iface lo inet loopback

auto eth0
iface eth0 inet dhcp
EOF

# Enable SSH (optional)
sudo chroot /mnt/microvm apk add openssh  # Alpine
# or
sudo chroot /mnt/microvm apt install -y openssh-server  # Debian

# Set up the root password
sudo chroot /mnt/microvm passwd -d root

sudo umount /mnt/microvm

Quick Start: Image Minimal

The fastest way to create an image for MicroVM:

#!/bin/bash
# Script: create-microvm-image.sh

set -e

IMAGE_SIZE="1G"
IMAGE_NAME="microvm-rootfs.img"
ALPINE_URL="https://dl-cdn.alpinelinux.org/alpine/v3.19/releases/x86_64/alpine-minirootfs-3.19.1-x86_64.tar.gz"

# Download the Alpine rootfs
wget -q --show-progress "$ALPINE_URL" -O alpine-rootfs.tar.gz

# Create the image
fallocate -l "$IMAGE_SIZE" "$IMAGE_NAME"
mkfs.ext4 -q -L microvm "$IMAGE_NAME"

# Mount and populate
MNT=$(mktemp -d)
sudo mount -o loop "$IMAGE_NAME" "$MNT"
sudo tar -xzf alpine-rootfs.tar.gz -C "$MNT"

# Basic config
echo "microvm" | sudo tee "$MNT/etc/hostname" > /dev/null
echo "nameserver 8.8.8.8" | sudo tee "$MNT/etc/resolv.conf" > /dev/null
echo "/dev/vda / ext4 defaults,noatime 0 1" | sudo tee "$MNT/etc/fstab" > /dev/null

# Enable auto login on ttyS0
sudo mkdir -p "$MNT/etc/init.d"
echo '#!/sbin/openrc-run' | sudo tee "$MNT/etc/init.d/autoconsole" > /dev/null
echo 'depend() { keyword timeout 0; }' | sudo tee -a "$MNT/etc/init.d/autoconsole" > /dev/null
echo 'start() { /sbin/getty -L ttyS0 115200 vt100 & }' | sudo tee -a "$MNT/etc/init.d/autoconsole" > /dev/null
sudo chmod +x "$MNT/etc/init.d/autoconsole"
sudo chroot "$MNT" rc-update add autoconsole default 2>/dev/null || true

sudo umount "$MNT"
rmdir "$MNT"

echo "Image created: $IMAGE_NAME"
echo "Kernel needed: vmlinuz (from host or custom build)"

Verifying the Image

# Check the filesystem
file microvm-rootfs.img
# Output: microvm-rootfs.img: Linux rev 1.0 ext4 filesystem data...

# Check mounting
sudo mount -o loop microvm-rootfs.img /mnt/microvm
ls /mnt/microvm/
sudo umount /mnt/microvm