Skip to main content

KVM/QEMU VM Setup on Arch Linux

A reference guide for spinning up Ubuntu VMs with sudo virsh — accessible via sudo virsh console and SSH.

1. Install Packages

sudo pacman -S qemu-full libvirt virt-manager virt-viewer dnsmasq \
openbsd-netcat libguestfs cloud-image-utils

bridge-utils is deprecated. iproute2 (installed by default) replaces it.


2. Configure libvirt to Use nftables

Edit /etc/libvirt/network.conf:

sudo nano /etc/libvirt/network.conf

Set:

firewall_backend = "nftables"

If VMs are not assigned an IP, try switching to iptables:

sudo systemctl start iptables.service
sudo systemctl enable iptables.service
sudo systemctl restart libvirtd.service

Then edit again and set:

firewall_backend = "iptables"

3. Enable Services

sudo systemctl enable --now libvirtd
sudo systemctl enable --now iptables # needed for NAT rules

4. Add Your User to the libvirt Group

sudo usermod -aG libvirt $USER

Log out and back in, or:

newgrp libvirt

5. Enable the Default NAT Network

sudo virsh net-start default
sudo virsh net-autostart default
sudo virsh net-list --all

6. Get the Ubuntu Cloud Base Image

wget -P /var/lib/libvirt/images/ \
https://cloud-images.ubuntu.com/noble/current/noble-server-cloudimg-amd64.img

Keep this as a shared base — all VMs will use it as a backing file.


7. Create a VM

7a. Create the Disk Overlay

sudo qemu-img create -f qcow2 \
-b /var/lib/libvirt/images/noble-server-cloudimg-amd64.img \
-F qcow2 \
/var/lib/libvirt/images/<vmname>.qcow2 <size>G

7b. Create cloud-init Config

# user-data
cat > /tmp/user-data <<EOF
#cloud-config
users:
- name: aditya
sudo: ALL=(ALL) NOPASSWD:ALL
groups: sudo
shell: /bin/bash
ssh_authorized_keys:
- $(cat ~/.ssh/id_ed25519.pub)
EOF
# meta-data
cat > /tmp/meta-data <<EOF
instance-id: <vmname>
local-hostname: <vmname>
EOF

7c. Build the Seed ISO

cloud-localds /var/lib/libvirt/images/<vmname>-seed.iso \
/tmp/user-data /tmp/meta-data

7d. Install the VM

sudo virt-install \
--name <vmname> \
--memory <ram> \
--vcpus <vcpus> \
--disk path=/var/lib/libvirt/images/<vmname>.qcow2,format=qcow2 \
--disk path=/var/lib/libvirt/images/<vmname>-seed.iso,device=cdrom \
--os-variant ubuntu24.04 \
--network network=default \
--graphics none \
--console pty,target_type=serial \
--import \
--noautoconsole

Examples

controlplane

2 vCPU • 4GB RAM • 15GB disk

sudo qemu-img create -f qcow2 \
-b /var/lib/libvirt/images/noble-server-cloudimg-amd64.img \
-F qcow2 \
/var/lib/libvirt/images/controlplane.qcow2 15G

sudo virt-install \
--name controlplane \
--memory 4096 \
--vcpus 2 \
--disk path=/var/lib/libvirt/images/controlplane.qcow2,format=qcow2 \
--disk path=/var/lib/libvirt/images/controlplane-seed.iso,device=cdrom \
--os-variant ubuntu24.04 \
--network network=default \
--graphics none \
--console pty,target_type=serial \
--import \
--noautoconsole

node01

2 vCPU • 2GB RAM • 10GB disk

sudo qemu-img create -f qcow2 \
-b /var/lib/libvirt/images/noble-server-cloudimg-amd64.img \
-F qcow2 \
/var/lib/libvirt/images/node01.qcow2 10G

sudo virt-install \
--name node01 \
--memory 2048 \
--vcpus 2 \
--disk path=/var/lib/libvirt/images/node01.qcow2,format=qcow2 \
--disk path=/var/lib/libvirt/images/node01-seed.iso,device=cdrom \
--os-variant ubuntu24.04 \
--network network=default \
--graphics none \
--console pty,target_type=serial \
--import \
--noautoconsole

8. Access the VM

# Serial console (Ctrl+] to exit)
sudo virsh console <vmname>

# Get IP
sudo virsh net-dhcp-leases default

# SSH
ssh aditya@<ip>

9. Common virsh Commands

sudo virsh list --all
sudo virsh start <vmname>
sudo virsh shutdown <vmname> # graceful
sudo virsh destroy <vmname> # force off
sudo virsh undefine <vmname> --remove-all-storage
sudo virsh snapshot-create-as <vmname> snap1
sudo virsh dominfo <vmname>