Running a Desktop Virtual Machine

This post is about my experience running a Linux desktop virtual machine on a Linux host. I used the VM interactively, often in fullscreen mode, to develop software and run web apps (mostly Slack and Google Docs). My experience wasn't great, as I ran into many challenges.

My previous post on running VMs discussed running bare-bones Linux VMs and getting SSH access to them. I usually do that for testing, where the VMs are light-weight, shortlived, and disposable. It turns out that long-lived desktop VMs have more challenging requirements and come with a whole new set of issues.

VM setup

Following a virt-install, I did many of the tasks I normally do for setting up a Linux host (see my configs repo), including setting the hostname, setting the timezone, configuring APT, installing packages, and configuring other software.

I renamed the username and group from debian to diego, while logged in as root:

usermod -d /home/diego -m debian
usermod -c 'Diego' -l diego debian
groupmod -n diego debian

I could have created a new user instead, but I guess I like having uid 1000.

CPU and RAM

For CPU, I typically assigned 4 vCPUs to the VM. Changing this value requires restarting the VM. Just because the VM has a vCPU doesn't mean it's using it, so it might be reasonable to set this to half or more of the host CPUs. I think 4 vCPUs is the bare minimum needed for software development with VS Code and Rust, and I probably should have allocated more.

For RAM, virt-manager has a field for the maximum allocation and another for the current allocation. Changing the maximum allocation requires restarting the VM, but changing the current allocation up to the maximum can happen at runtime.

I created an additional swap disk to relieve memory pressure. Using a separate disk image was convenient because I didn't have to copy it every time when moving the VM to a different host. I still copied the swap image the first time because the VM's /etc/fstab referred to the swap partition's PARTUUID.

I also set browser.tabs.unloadOnLowMemory to true in Firefox, which may help with memory pressure.

Display

For video settings, I tried a few different options. My main problem was that I encountered serious rendering glitches with Google Docs in Firefox. I ultimately used Virtio with 3D Acceleration on and a display type of Spice Server with OpenGL on. However, I set gfx.canvas.accelerated to false in Firefox to work around the Google Docs rendering glitches.

I wanted the VM to be able to run in both windowed and fullscreen modes, which for my widescreen monitor meant 1792x1344 and 5120x1440 resolutions.

The default display resolutions were missing 5120x1440. I created /etc/X11/xorg.conf.d/10-monitor.conf:

Section "Monitor"
	Identifier "Virtual-1"
	Modeline "5120x1440_60.00"  624.50  5120 5496 6048 6976  1440 1443 1453 1493 -hsync +vsync
	Option "PreferredMode" "1792x1344"
EndSection

I got that Modeline by running:

$ sudo apt install xcvt
[...]
$ cvt 5120 1440
# 5120x1440 59.96 Hz (CVT) hsync: 89.52 kHz; pclk: 624.50 MHz
Modeline "5120x1440_60.00"  624.50  5120 5496 6048 6976  1440 1443 1453 1493 -hsync +vsync

I found that the Xfce Display Settings app was causing the VM to enter 5120x1440 mode on login, even though I wanted it to default to 1792x1344. I think I removed .config/xfce4/xfconf/xfce-perchannel-xml/displays.xml and then never opened the Xfce Display Settings again to work around that.

I created two scripts and two launcher buttons on the Xfce panel to switch between windowed and fullscreen modes. The video-windowed script:

#!/bin/sh
exec xrandr --output Virtual-1 --mode 1792x1344

And the video-fullscreen script:

#!/bin/sh
exec xrandr --output Virtual-1 --mode 5120x1440_60.00

Finally, I ran into an issue with the Notion window manager when running the VM in windowed mode. I normally hold the Meta key and drag the right mouse button to resize windows. This doesn't work. I could resize a window by dragging the left mouse button on the window border, but that border so thin that it's hard to hit. As a workaround, I enabled Shift+Meta while dragging windows in the VM, in .notion/cfg_bindings.lua:

     bdoc("Resize the frame."),
     mdrag("Button1@border", "WFrame.p_resize(_)"),
     mdrag(META.."Button3", "WFrame.p_resize(_)"),
+    mdrag("Shift+"..META.."Button3", "WFrame.p_resize(_)"),

Suspend

I wanted to be able to keep this VM "turned on" even while the host computer was suspended or hibernated.

Early on, I found that running suspend or hibernate within the VM did not work, and I disabled it:

sudo systemctl mask sleep.target suspend.target hibernate.target hybrid-sleep.target

The Xfce logout dialog stops showing the buttons to take these disabled actions, which is nice.

I also found that I couldn't pause and resume the VM in virt-manager successfully.

I could generally suspend the host, and the VM would tolerate that.

However, in March 2025, the VM kept crashing when the host suspended. I upgraded the qemu-* and seabios packages to the Debian 12 backports versions (and had to install qemu-system-modules-spice), which resolved the suspend issue.

Shared filesystem

I set up a Virtio filesystem to share files between the host and the VM. In virt-manager, I used virtio-9p because virtiofs wasn't supported when running libvirt under in user (session) mode:

Unable to add device: unsupported configuration: virtiofs is not yet supported in session mode

The target path in virt-manager ends up being the name of the 9p share in the VM. I added this to /etc/fstab in the VM:

/home/diego/host-share /home/diego/host-share 9p trans=virtio,version=9p2000.L,posixacl,msize=104857600 0 2

I don't remember where I found these options; it may have been the QEMU wiki.

Disk image size and VM migration

I occasionally moved this VM from a desktop to a laptop and vice versa. I didn't set up any fancy online migration; I just shut it down and copied the disk image.

Ideally, I'd free up some space and shrink the disk image prior to copying it. I have discard enabled in the VM's /etc/fstab. I found I had to change the virt-manager disk's Discard mode setting from Hypervisor default to unmap. Then I ran:

sudo fstrim -v /

within the VM, which reported that it trimmed many gigabytes. The qcow2 image shrank in size accordingly on the host. The manual fstrim may not be necessary if you configure virt-manager from the start.

I used rsync to copy the disk image, with the --compress and --sparse flags. I probably should have used the --inplace flag also (which used to conflict with --sparse but doesn't anymore).

To copy the VM's configuration, I ran this on the original host:

virsh dumpxml $NAME > $NAME.xml

And then imported it on the new host:

virsh define --file $NAME.xml

I also needed to change the display Splice device to auto, but that might be because I had messed with it on the original host.

Keyboard

I set Caps Lock to be Control on some keyboards. I found I had to do this again within the VM by setting:

XKBOPTIONS="ctrl:nocaps"

in /etc/default/keyboard.

Webcam

I tried passing through a USB webcam for video conferencing, but this was too slow to work reliably. I don't have a good solution to this. My workaround was to run the video conferencing on the host.

Conclusion

That's it. I used this VM for months, so I don't think I'd find new issues beyond these. The good news is that most of these are either easy to work around or acceptable limitations, with some patience. My top remaining issues are:

  • No webcam support,
  • No ability to suspend/hibernate/pause the VM, and
  • No dynamic CPU allocation.

Webcam support is important, especially because video conferencing often needs to be authenticated, and that account may "belong" inside the VM. I don't know how to solve it today, other than perhaps passing a PCIe USB card through to the VM. The last two are "software issues" and might well be solvable with some more configuration effort.