Notes on Creating Headless VMs with QEMU/KVM
I recently needed to run some virtual machines with Debian as the OS. I wanted to create them to be headless (no console process), without using a GUI, and with KVM/QEMU. (I will eventually need to get this working on an arm64 system, none of which Vagrant, Virtualbox, etc. support.) I’m recording some notes here for myself since a number of the resources I found helped point in the right direction but didn’t do exactly what I wanted.
Install KVM/QEMU/libvirt/guest tools
To install the various components:
$ sudo apt install --no-install-recommends qemu-system libvirt-clients libvirt-daemon-system libguestfs-tools virtinst
$ sudo adduser rnowling libvirt
Create Network If Needed
At some point or another, I think I deleted the default network. Here are some steps for recreating it:
$ sudo virsh net-list --all
$ sudo virsh net-define /usr/share/libvirt/networks/default.xml
Network default defined from /usr/share/libvirt/networks/default.xml
$ sudo virsh net-autostart default
Network default marked as autostarted
$ sudo virsh net-start default
Network default started
$ sudo virsh net-list --all
Prepare the Guest Image
Download image:
$ wget https://cloud.debian.org/images/cloud/bookworm/latest/debian-12-generic-amd64.qcow2
Create the DHCP settings config file called 50-dhcp.yaml
:
# 50-dhcp.yaml
network:
version: 2
ethernets:
all-en:
match:
name: "en*"
dhcp4: true
dhcp6: true
Copy the original image so that we can don’t modify the original:
$ cp debian-12-generic-amd64.qcow2 debian-12-generic-amd64-prepared.qcow2
Create a non-root user that cannot login with a password, add my SSH key, run ssh-keygen and restart sshd (fails the first time without system keys), and copy over the DHCP networking config details.
$ sudo virt-customize -a debian-12-generic-amd64-prepared.qcow2 \
--run-command "adduser debian -q" \
--run-command "adduser debian sudo" \
--ssh-inject debian:file:/home/rnowling/.ssh/id_ed25519.pub \
--firstboot-command 'ssh-keygen -A && systemctl restart sshd' \
--copy-in 50-dhcp.yaml:/etc/netplan
Copy the prepared image to an image to be used for that specific VM instance. The
image needs to be in a directory that the libvirt-qemu
user can read and write.
$ sudo cp debian-12-generic-amd64-prepared.qcow2 /var/lib/libvirt/images/debian12-test.qcow
Start the virtual machine instance. The image is imported directly (no install process is run). The graphical interface (VNC) and console are disabled.
$ sudo virt-install --name debian12-test \
--memory 8192 \
--vcpus 4 \
--disk /var/lib/libvirt/images/debian12-test.qcow \
--import \
--os-variant debian11 \
--network default \
--graphics none \
--noautoconsole
SSH into VM
Once running, we can grab the IP address from the DHCP lease:
$ sudo virsh net-dhcp-leases default
Expiry Time MAC address Protocol IP address Hostname Client ID or DUID
------------------------------------------------------------------------------------------------------------------------------------------------
2024-12-23 15:59:31 52:54:00:41:d4:17 ipv4 192.168.122.175/24 - ff:56:50:4d:98:00:02:00:00:ab:11:e1:1e:c0:68:36:f5:c2:9c
and SSH into the VM:
$ ssh debian@192.168.122.175
The authenticity of host '192.168.122.175 (192.168.122.175)' can't be established.
ED25519 key fingerprint is SHA256:Nc3Eiu9nxXF56YvdshEb5FDiXkHPV4Oq/hymCvxwuCE.
This key is not known by any other names.
Are you sure you want to continue connecting (yes/no/[fingerprint])? yes
Warning: Permanently added '192.168.122.175' (ED25519) to the list of known hosts.
Linux localhost 6.1.0-28-amd64 #1 SMP PREEMPT_DYNAMIC Debian 6.1.119-1 (2024-11-22) x86_64
The programs included with the Debian GNU/Linux system are free software;
the exact distribution terms for each program are described in the
individual files in /usr/share/doc/*/copyright.
Debian GNU/Linux comes with ABSOLUTELY NO WARRANTY, to the extent
permitted by applicable law.
Last login: Mon Dec 23 21:10:41 2024 from 192.168.122.1
debian@localhost:~$
Update 12/30/2024: Arm64 Support
I tested these instructions on arm64 using my newly-acquired System75 Thelio Astra.
Except for needing to use the debian-12-genericcloud-arm64.qcow2
image, everything documented above worked without issue. I ran
into a small hiccup when trying to remove the VM. The VM seems to be created with a NVRam backing file, so deleting the VM
requires passing the --nvram
flag to virsh undefine
.
References
I found the following references helpful when figuring out how to get this working.