Create a basic Debian 13 VM using the QEMU CLI

I wanted to build my step-by-step understanding of the QEMU CLI.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
sudo qemu-system-aarch64 \
    -machine virt \
    -cpu host \
    -accel hvf \
    -m 4G \
    -drive if=pflash,format=raw,readonly=on,file=$HOME/vms/edk2-aarch64-code.fd \
    -drive if=pflash,format=raw,file=$HOME/vms/debian-13/efivars.fd \
    -drive if=none,id=hd,file=$HOME/vms/debian-13/disk.qcow2,format=qcow2 \
    -drive if=none,id=cd,file=$HOME/vms/debian-13/debian-13.5.0-arm64-netinst.iso,media=cdrom,format=raw \
    -device virtio-blk-pci,drive=hd \
    -device virtio-blk-pci,drive=cd \
    -netdev vmnet-shared,id=net0,start-address=192.168.222.2,end-address=192.168.222.254,subnet-mask=255.255.255.0 \
    -device virtio-net-pci,netdev=net0 \
    -device virtio-gpu-pci \
    -device qemu-xhci \
    -device usb-kbd \
    -device usb-tablet \
    -display cocoa

This is the command that gave me a working Debian 13 ARM VM on my M5 MacBook Pro.

Basics

1
qemu-system-aarch64

This is the full-system QEMU binary for aarch64. With -accel hvf below and an arm64 guest on an arm64 host, this is virtualisation, not emulation — the guest runs natively.

-machine virt

The machine to emulate. I sort of imagine it like choosing a motherboard. This analogy makes more sense on x86 with options like i440FX or q35 emulating actual machines. For aarch64 we set this to virt or one of the various options. Use qemu-system-aarch64 -machine help for all the available options.

1
-cpu host

This passes the host CPU through to the guest. You can also choose a specific ARM CPU/core here. Use qemu-system-aarch64 -cpu help for all options.

1
-accel hvf

This picks the hypervisor. On macOS this is Apple’s Hypervisor framework (hvf = HyperVisor.Framework), not to be confused with the higher-level Virtualization framework that tools like UTM use.

Note: QEMU 11.0.0 and 11.0.1 have a bug in their HVF SME2 support. There exists a patch which however has not yet landed upstream. I’m using this fork where the patch is currently developed.

1
-m 4G

Use 4GB of memory.

Devices

Devices can be seen as backend/frontend. Options like -drive and -netdev create the device. This might be a disk, or a network interface. These usually need some sort of configuration.

To attach the devices to the VM use the -device flag. -device takes a driver, e.g. virtio-blk-pci (a paravirtualised driver for VMs), and references the backend by its id (set in the matching -drive option).

1
2
3
4
5
6
-drive if=pflash,format=raw,readonly=on,file=$HOME/vms/edk2-aarch64-code.fd \
-drive if=pflash,format=raw,file=$HOME/vms/debian-13/efivars.fd \
-drive if=none,id=hd,file=$HOME/vms/debian-13/disk.qcow2,format=qcow2 \
-drive if=none,id=cd,file=$HOME/vms/debian-13/debian-13.5.0-arm64-netinst.iso,media=cdrom,format=raw \
-device virtio-blk-pci,drive=hd \
-device virtio-blk-pci,drive=cd \

This sets up four drives. The first two are the UEFI firmware and EFI variables, since there’s no legacy BIOS boot for aarch64. The other two are the Debian 13 install media and the qcow2 disk where the OS will be installed; the disk can be created with qemu-img create -f qcow2 disk.qcow2 64G. The CD and the disk are then attached to the VM with -device. The firmware and EFI variables don’t need to be attached; they’re flash memory, mapped directly into the guest’s address space.

1
2
-netdev vmnet-shared,id=net0,start-address=192.168.222.2,end-address=192.168.222.254,subnet-mask=255.255.255.0 \
-device virtio-net-pci,netdev=net0 \

Configure the network for the VM. I use vmnet-shared which is a NATed network connection for the VM. This also uses macOS specific functionality and requires higher privileges, hence the sudo for the QEMU call.

1
2
3
4
-device virtio-gpu-pci \
-device qemu-xhci \
-device usb-kbd \
-device usb-tablet \

Add a GPU, a USB controller, and a USB keyboard and tablet. A tablet works better than a mouse to sync the host mouse position to the VM. virtio drivers for keyboard and mouse exist, but GRUB and the UEFI firmware don’t support them — only the Linux kernel does — so input wouldn’t work in the bootloader. USB HID works at every stage.

1
-display cocoa

Uses macOS Cocoa to show the VM’s screen. Alternatively, -nographic gives a serial console instead of a window, handy for a headless setup. The Debian installer worked fine for me over -nographic.