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-utilsis 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>