Skillversitas

Running a MicroVM

Once the kernel and rootfs image are ready, it is time to run the MicroVM. This article covers the basic command, machine type options, and complete configurations.

Basic Command

KVM Mode (Linux with hardware virtualization)

qemu-system-x86_64 \
  -M microvm \
  -enable-kvm \
  -cpu host \
  -m 256m \
  -smp 2 \
  -kernel vmlinuz \
  -append "console=ttyS0 root=/dev/vda rw" \
  -drive id=rootfs,file=microvm-rootfs.img,format=raw,if=none \
  -device virtio-blk-device,drive=rootfs \
  -nographic \
  -serial stdio

TCG Mode (macOS or without KVM)

qemu-system-x86_64 \
  -M microvm \
  -cpu qemu64 \
  -m 256m \
  -smp 1 \
  -kernel vmlinuz \
  -append "console=ttyS0 root=/dev/vda rw" \
  -drive id=rootfs,file=microvm-rootfs.img,format=raw,if=none \
  -device virtio-blk-device,drive=rootfs \
  -nographic \
  -serial stdio

Parameter Reference

Parameter Purpose
-M microvm Use the MicroVM machine type
-enable-kvm Enable KVM acceleration
-cpu host Use the host CPU features
-m 256m Allocate 256MB of RAM
-smp 2 2 virtual CPU cores
-kernel vmlinuz Path to the Linux kernel
-append "..." Kernel command line arguments
-drive ... Define a block device
-device virtio-blk-device Virtio block device for the drive
-nographic Disable the GUI, output to the terminal
-serial stdio Route the serial port to the terminal

MicroVM Without Legacy Devices

The most minimal mode for maximum performance:

qemu-system-x86_64 \
  -M microvm,x-option-roms=off,pit=off,pic=off,isa-serial=off,rtc=off \
  -enable-kvm \
  -cpu host \
  -m 256m \
  -smp 2 \
  -kernel vmlinuz \
  -append "console=hvc0 root=/dev/vda rw" \
  -nographic \
  -chardev stdio,id=virtiocon0 \
  -device virtio-serial-device \
  -device virtconsole,chardev=virtiocon0 \
  -drive id=rootfs,file=microvm-rootfs.img,format=raw,if=none \
  -device virtio-blk-device,drive=rootfs

What Changed

  • PIT off - Disable the i8254 timer (requires TSC_DEADLINE on the CPU)
  • PIC off - Disable the i8259 interrupt controller
  • ISA serial off - Disable the ISA serial port, use virtconsole
  • RTC off - Disable the real-time clock, use kvmclock
  • console=hvc0 - Console via virtio-serial instead of ttyS0

Machine Type Configuration

Available Options

microvm.x-option-roms=bool    # Load option ROMs (default: on)
microvm.pit=OnOffAuto          # i8254 PIT timer
microvm.isa-serial=bool        # ISA serial port
microvm.pic=OnOffAuto          # i8259 PIC
microvm.rtc=OnOffAuto          # MC146818 RTC
microvm.auto-kernel-cmdline=bool  # Automatically add virtio-mmio to the cmdline

Example Option Combinations

# Maximum compatibility mode (default)
-M microvm

# Performance mode (no legacy devices, requires KVM + TSC_DEADLINE)
-M microvm,pit=off,pic=off,rtc=off

# Mode without option ROMs
-M microvm,x-option-roms=off

# Mode with the serial port on, everything else off
-M microvm,pit=off,pic=off,rtc=off,isa-serial=on

Network Configuration

TAP Network (Root access)

# Create the TAP interface first
sudo ip tuntap add dev tap0 mode tap
sudo ip addr add 10.0.0.1/24 dev tap0
sudo ip link set tap0 up

# Run the MicroVM with network
qemu-system-x86_64 \
  -M microvm \
  -enable-kvm -cpu host -m 256m -smp 2 \
  -kernel vmlinuz \
  -append "console=ttyS0 root=/dev/vda rw ip=dhcp" \
  -drive id=rootfs,file=microvm-rootfs.img,format=raw,if=none \
  -device virtio-blk-device,drive=rootfs \
  -netdev tap,id=tap0,script=no,downscript=no \
  -device virtio-net-device,netdev=tap0 \
  -nographic -serial stdio

# Inside the VM, set up the IP:
# ip addr add 10.0.0.2/24 dev eth0
# ip link set eth0 up
# ip route add default via 10.0.0.1

User Mode Networking (No Root)

qemu-system-x86_64 \
  -M microvm \
  -enable-kvm -cpu host -m 256m -smp 2 \
  -kernel vmlinuz \
  -append "console=ttyS0 root=/dev/vda rw ip=dhcp" \
  -drive id=rootfs,file=microvm-rootfs.img,format=raw,if=none \
  -device virtio-blk-device,drive=rootfs \
  -netdev user,id=net0,hostfwd=tcp::2222-:22 \
  -device virtio-net-device,netdev=net0 \
  -nographic -serial stdio

Port Forwarding

Forward a host port to a VM port:

# SSH into the VM via port 2222
ssh -p 2222 root@localhost

# Forward an HTTP port
-netdev user,id=net0,hostfwd=tcp::8080-:80

Shutting Down a MicroVM

Since MicroVM has no ACPI, shutdown is done via a triple fault:

# Inside the guest Linux:
reboot -f

# Or power it off from the host:
# Ctrl+A, X (in the QEMU monitor)

Auto Shutdown from the Host

# Run with no-reboot (QEMU exits when the guest reboots)
qemu-system-x86_64 \
  -M microvm \
  -enable-kvm -cpu host -m 256m \
  -kernel vmlinuz \
  -append "console=ttyS0 root=/dev/vda rw reboot=t" \
  -drive id=rootfs,file=microvm-rootfs.img,format=raw,if=none \
  -device virtio-blk-device,drive=rootfs \
  -nographic -serial stdio \
  -no-reboot

The reboot=t kernel cmdline parameter forces Linux to use a triple fault for reboot, and -no-reboot makes QEMU exit when a reboot occurs.

Complete Script

#!/bin/bash
# Script: run-microvm.sh

KERNEL="./vmlinuz"
ROOTFS="./microvm-rootfs.img"
RAM="256m"
CPUS="2"
PORT_SSH="2222"

if [ ! -f "$KERNEL" ]; then
  echo "Error: Kernel not found at $KERNEL"
  exit 1
fi

if [ ! -f "$ROOTFS" ]; then
  echo "Error: Rootfs image not found at $ROOTFS"
  exit 1
fi

exec qemu-system-x86_64 \
  -M microvm,pit=off,pic=off,rtc=off \
  -enable-kvm \
  -cpu host \
  -m "$RAM" \
  -smp "$CPUS" \
  -kernel "$KERNEL" \
  -append "console=ttyS0 root=/dev/vda rw ip=dhcp reboot=t" \
  -drive id=rootfs,file="$ROOTFS",format=raw,if=none \
  -device virtio-blk-device,drive=rootfs \
  -netdev user,id=net0,hostfwd=tcp::${PORT_SSH}-:22 \
  -device virtio-net-device,netdev=net0 \
  -nographic \
  -serial stdio \
  -no-reboot