To isolate my development dependencies from my host system, I want to run a VM locally. I'd like to use QEMU for virtualization as it's simple and performant.
qemu-headless so that I have QEMU present without the GUI. I
edk2-ovmf to enable UEFI support for virtual machines.
sudo pacman -S qemu-headless edk2-ovmf
I create an image to use as the backing storage for the VM. I use raw as the format for performance and because I don't care about snapshot functionality right now. I preallocate the image to ensure I don't run out of space on my host machine unexpectedly in the future. Preallocating should also give better performance, but I haven't tested this.
qemu-img create -f raw -o preallocation=full ~/var/qemu/work-development.img 40G
To install the OS (Arch Linux) I download the ISO. Then, I make a copy of the non-volatile variable store for the virtual machine. Then, I launch QEMU with the ISO and backing storage attached.
cp /usr/share/edk2-ovmf/x64/OVMF_VARS.fd ~/var/qemu/work-development-uefi-vars.fd qemu-system-x86_64 -cdrom ~/Downloads/archlinux-2020.11.01-x86_64.iso -boot order=d\ -drive if=virtio,file=$HOME/var/qemu/work-development.img,format=raw\ -drive if=pflash,format=raw,readonly,file=/usr/share/edk2-ovmf/x64/OVMF_CODE.fd\ -drive if=pflash,format=raw,file=$HOME/var/qemu/work-development-uefi-vars.fd
Note that we use
if=virtio for the backing image because we want to use the
paravirtualized virtio drivers, which provide better performance and overhead.
$HOME in place of
~ above. This is because for some reason QEMU
gives the following error when using
~ with a
-drive specified with an
Could not open '~/var/qemu/work-development-uefi-vars.fd': No such file or directory
QEMU helpfully starts a local VNC server for us to connect to.
VNC server running on ::1:5900
tigervnc and connected to the VNC server.
sudo pacman -S tigervnc vncviewer :5900
It turned out that the system had too little memory to boot, so I needed to re-launch it with more memory.
qemu-system-x86_64 -cdrom ~/Downloads/archlinux-2020.11.01-x86_64.iso -boot order=d\ -drive if=virtio,file=$HOME/var/qemu/work-development.img,format=raw\ -drive if=pflash,format=raw,readonly,file=/usr/share/edk2-ovmf/x64/OVMF_CODE.fd\ -drive if=pflash,format=raw,file=$HOME/var/qemu/work-development-uefi-vars.fd\ -m 2G vncviewer :5900
If you enter fullscreen mode in VNC and have trouble exiting, press
open the context menu. You should be able to disable fullscreen from there.
Once the machine booted, I verified that my network interface was listed and enabled.
I tested that I had a working internet connection.
I ensured that the EFI vars were available:
I updated the system clock.
timedatectl set-ntp true
I partitioned the disk after identifying it with
I didn't want to get fancy, so I chose
gpt as the label type and created two
partitions: boot and root. The boot partition was
512M in size with
partition type code
ef00 (EFI system partition) and label
boot and the root
partition took up the remainder of the free space (~39.5G) with partition
8600 (Linux filesystem) and label
I didn't create a swap partition.
I formatted the newly created partitions.
mkfs.fat -F32 /dev/vda1 mkfs.ext4 /dev/vda2
I mounted the root volume to
/mnt and the boot volume to
mount /dev/vda2 /mnt mkdir /mnt/boot mount /dev/vda1 /mnt/boot
I used reflector to select the fastest, most recently updated mirrors.
pacman -Sy reflector reflector --age 12 --protocol https --sort rate --save /etc/pacman.d/mirrorlist
I used pacstrap to install the base system.
pacstrap /mnt base base-devel linux linux-firmware vim intel-ucode
I generated an fstab file.
genfstab -U /mnt >> /mnt/etc/fstab
I changed root into the new system.
Within the chroot, I selected my time zone and ran hwclock to generate
ln -sf /usr/share/zoneinfo/Canada/Eastern /etc/localtime hwclock --systohc
I edited my
/etc/locale.gen and uncommented
en_CA.UTF-8 UTF-8 to enable my
locale. I then generated the locales.
vim /etc/locale.gen locale-gen
I created the locale.conf file and set the
echo "LANG=en_CA.UTF-8" > /etc/locale.conf
I created a hostname file and added matching entries to hosts.
echo hephaestus > /etc/hostname echo "127.0.1.1 hephaestus.olympus.hax.rs hephaestus" > /etc/hosts
I set the root password:
I installed systemd-boot as my bootloader:
I configured systemd-boot by creating a
with the contents:
title Arch Linux linux /vmlinuz-linux initrd /intel-ucode.img initrd /initramfs-linux.img options root="UUID=..." rw
I also created a
/boot/loader/loader.conf file with the contents:
default arch.conf console-mode max
I left the chroot, unmounted the partitions, and rebooted the machine.
exit umount -R /mnt reboot
I confirmed that I successfully booted into my new Arch Linux install, then I shutdown the VM.
I wanted to add some performance improvements. First, I had to switch from using DHCP on my interface
to a DHCP created on a bridge. I removed my
/etc/systemd/network/20-wired.network file and replaced it with
[NetDev] Name=br0 Kind=bridge
[Match] Name=enp0s31f6 [Network] Bridge=br0
[Match] Name=br0 [Network] DHCP=ipv4 [DHCP] UseDomains=true
systemd-networkd and made sure I still had internet access:
sudo systemctl restart systemd-networkd ping archlinux.org
I wanted to use the qemu-bridge-helper
to create a tap device for me, so that my guest could talk to the external network directly.
To do so, I added an ACL file telling QEMU that the
br0 interface should be whitelisted:
sudo mkdir /etc/qemu echo "allow br0" | sudo tee -a /etc/qemu/bridge.conf sudo chown root:kvm /etc/qemu/bridge.conf sudo chmod 0640 /etc/qemu/bridge.conf
Then, I added my user to the
sudo usermod -a -G kvm indiv0
Now, QEMU would automatically configure the bridge for me if I launched it
with the arguments
-net nic -net bridge,br=br0 but the guest would not be
able to connect to the network, because the firewall was still blocking all
traffic. To solve this, I configured iptables to allow all traffic to be
forwarded across the bridge:
sudo iptables -I FORWARD -m physdev --physdev-is-bridged -j ACCEPT
Next, I started my VM with extra options for improved performance.
- enabled KVM for hardware acceleration
-cpu hostto make QEMU emulate the host's exact CPU
-smp $(nproc)to provide the VM access to all available cores
- assigned the VM half my RAM with
- enabled virtio drivers for the network device with
- disabled the cache for the raw disk image
- used native Linux AIO instead of userspace threads
- using tap devices for networking
- enabling the
virtio-balloondevice for potential memory reclamation
qemu-system-x86_64\ -enable-kvm\ -cpu host\ -smp $(nproc)\ -device virtio-balloon\ -net nic,model=virtio -net bridge,br=br0\ -drive if=virtio,file=$HOME/var/qemu/work-development.img,format=raw,aio=native,cache=none\ -drive if=pflash,format=raw,readonly,file=/usr/share/edk2-ovmf/x64/OVMF_CODE.fd\ -drive if=pflash,format=raw,file=$HOME/var/qemu/work-development-uefi-vars.fd\ -m 16G
Inside the VM I added a
connect the VM to my external network:
[Match] Name=ens* [Network] DHCP=yes [DHCP] UseDomains=true
Then I enabled and started the
systemctl start systemd-networkd systemctl enable systemd-networkd systemctl start systemd-resolved systemctl enable systemd-resolved
I also needed to replace the default
resolv.conf with the systemd one.
rm /etc/resolv.conf ln -s /usr/lib/systemd/resolv.conf /etc/resolv.conf
I added my user to the machine:
useradd -m npekin passwd npekin
Add the new user to the sudoers:
I installed openssh and enabled the sshd service so that I could connect to the VM over SSH instead of over VNC:
pacman -S openssh systemctl start sshd systemctl enable sshd
From the host I connected to the VM via SSH and copied over my key:
ssh-copy-id npekin@hephaestus ssh npekin@hephaestus
I generated a new SSH key on the VM and uploaded it to my GitHub account. I then installed git and cloned my dotfiles repo. Using my dotfiles bootstrap script, I installed the necessary packages onto the machine.
ssh-keygen -t ed25519 sudo pacman -S git git clone email@example.com:indiv0/dotfiles etc cd etc make ./bootstrap.sh
I modified the
/etc/ssh/sshd_config to allow X11 forwarding by setting
X11Forwarding to yes:
sudo vim /etc/ssh/sshd_config sudo systemctl restart sshd
This allows to me SSH into the VM with X11 forwarding enabled and launch graphical applications:
ssh -X hephaestus firefox