These notes capture the working process for developing, testing, and releasing pve-microvm. They are intentionally generic: replace hostnames, node IPs, storage names, VMIDs, and template IDs with values from your own Proxmox VE cluster.
pve-microvm is a Debian package that adds QEMU microvm machine type support to Proxmox VE by:
- Installing
PVE::QemuServer::MicroVMas a delegated command builder. - Patching PVE's
Machine.pmandQemuServer.pmsomachine: microvmis accepted and dispatched to the microVM builder. - Shipping a minimal kernel/initrd pair for direct kernel boot.
- Adding CLI helpers, template creation from OCI images, filesystem sharing, vsock support, and web UI integration.
The patching model is invasive by design, so every change must be reversible, idempotent, and tested against the current PVE qemu-server package.
- Work on
mainunless explicitly creating a long-running branch. - Do not rebase published history.
- Keep commits outcome-focused: one logical fix or feature per commit.
- For release builds, update
debian/changelog, tagvX.Y.Z, push the commit and tag, then let GitHub Actions publish assets. - Installation instructions should not hardcode package versions. Prefer commands that resolve the latest release asset dynamically.
- After a release asset is published, test on one non-critical node first before pushing to the rest of the cluster.
Typical release sequence:
# after committing changelog and code changes
git tag -a v0.3.X -m "v0.3.X — short release description"
git push
git push origin v0.3.X
# wait for GitHub release assets, then on a test PVE node:
curl -sLO $(curl -s https://api.github.com/repos/rcarmo/pve-microvm/releases/latest \
| grep browser_download_url | grep '.deb' | cut -d'"' -f4)
dpkg -i pve-microvm_*.deb
apt-get install -fRun the checks that are meaningful in the current environment:
- Shell syntax for scripts:
bash -n tools/pve-microvm-template
bash -n tools/pve-microvm-patch
bash -n kernel/build-kernel.sh- Perl syntax for
MicroVM.pmis best checked on a PVE host, not on a generic development machine, because it imports PVE Perl modules:
perl -c /usr/share/pve-microvm/MicroVM.pm
perl -e 'use PVE::QemuServer; use PVE::QemuServer::MicroVM; print "OK\n"'- Verify patches are idempotent:
pve-microvm-patch status
pve-microvm-patch apply
pve-microvm-patch apply
pve-microvm-patch statusThe second apply must not duplicate imports or delegation blocks.
Use a staged rollout:
- Pick a non-critical PVE node that has local storage and at least one disposable test VM/template.
- Install the new
.deb. - Restart
pvedaemonand verify it still starts. - Create or restart a microVM.
- Verify the QEMU command line uses
-M microvmand virtio-only devices. - Verify the guest boots, mounts root as
/dev/vda, has networking, and the QEMU guest agent responds if enabled. - Only then deploy to the remaining nodes.
Useful checks on a PVE node:
systemctl restart pvedaemon
systemctl is-active pvedaemon
qm config <vmid> | grep '^machine: microvm'
qm start <vmid>
PID=$(pgrep -f "kvm.*-id <vmid>")
tr '\0' '\n' < /proc/$PID/cmdline | grep -A1 '^-machine$'
tr '\0' '\n' < /proc/$PID/cmdline | grep -E 'virtio-blk|virtio-net|balloon|scsi-hd'
qm agent <vmid> ping
qm guest exec <vmid> -- bash -c 'hostname; df -h /; ip -brief addr'Expected microVM signs:
- Machine line contains
microvm,x-option-roms=off,...,pcie=on. - Block devices are
virtio-blk-pci-non-transitional, notscsi-hd. - Network devices are virtio PCI devices.
- Root filesystem is mounted from
/dev/vdafor Linux guests.
When upgrading a node that already runs microVMs:
- Record the VMIDs and expected services before stopping anything.
- Restart one microVM at a time unless the host itself must reboot.
- After restart, check the guest agent, root filesystem, and service health.
- For web services, test from both inside the guest and from the host/network.
Example pattern:
qm stop <vmid>; sleep 3; qm start <vmid>
sleep 15
qm agent <vmid> ping
qm guest exec <vmid> -- bash -c 'hostname; df -h /; systemctl --failed --no-pager'For HTTP services:
IP=$(qm guest exec <vmid> -- bash -c 'hostname -I' \
| python3 -c 'import json,sys; print(json.load(sys.stdin).get("out-data","").split()[0])')
curl -I http://$IP:<port>/onboot: 1 VMs expose ordering bugs. If PVE starts VMs before pve-microvm patches are active, machine: microvm may be rejected or stripped during config parsing/migration, and the VM can start as a standard PC. The disk will then appear as /dev/sda instead of /dev/vda, which looks like a lost root filesystem.
Required safeguards:
pve-microvm-early.servicemust be enabled on every node.- It must run before
pvedaemon.serviceandpve-guests.service. - The patch script must be idempotent.
Check:
systemctl is-enabled pve-microvm-early.service
systemctl show pve-microvm-early.service | grep -E 'Before=|After='
systemctl status pve-microvm-early.service --no-pagerExpected ordering includes:
Before=pvedaemon.service pve-guests.service
After any host reboot, verify at least one auto-started microVM actually launched as microvm and not pc+pve0/q35.
Avoid partial PVE upgrades. PVE Perl modules are tightly coupled, and mismatched versions can break pvedaemon, qm, and config parsing. Prefer full upgrades:
apt-get update
apt-get dist-upgradeThen:
dpkg --configure -a
apt-get install -f
pve-microvm-patch apply
systemctl restart pvedaemonWatch for these failure modes:
unknown file 'sdn/...cfg'fromPVE::Clusterusually means PVE package version skew.parse_guest_agentor similar Perl API errors usually meanqemu-serverchanged an internal function signature; updateMicroVM.pmcompatibility code.- Duplicate
use PVE::QemuServer::MicroVM;or duplicate delegation blocks mean patch idempotency regressed.
If a PVE package upgrade replaces patched files, the dpkg trigger and early-boot service should re-apply patches. Still verify explicitly:
grep -n 'PVE::QemuServer::MicroVM\|is_microvm' /usr/share/perl5/PVE/QemuServer.pm
perl -e 'use PVE::QemuServer; print "OK\n"'Kernel upgrades can fail if the EFI system partition is full, especially on small appliances. Symptoms include No space left on device while copying initrd.img-* into /boot/efi/....
Check before/after upgrades:
df -h /boot /boot/efi
dpkg -l 'proxmox-kernel-*-pve-signed' | awk '/^ii/{print $2, $3}'
uname -rRemove old kernels, keeping the currently running kernel and one known-good fallback:
apt-get remove proxmox-kernel-<old-version>-pve-signed
apt-get autoremoveIf the bootloader selected an unwanted kernel, pin the intended version:
proxmox-boot-tool kernel pin <version>-pve
update-grubReboot only after dpkg --configure -a is clean and /boot/efi has enough free space.
Current memory features include:
free-page-reporting=onon virtio-balloon.deflate-on-oom=onon virtio-balloon.- PVE active ballooning via
balloonconfig. - Optional
virtio-mem-pciwith a memory backend.
Check a running VM command line:
PID=$(pgrep -f "kvm.*-id <vmid>")
tr '\0' '\n' < /proc/$PID/cmdline | grep balloonExpected balloon flags:
virtio-balloon-pci-non-transitional,id=balloon0,free-page-reporting=on,deflate-on-oom=on
QMP balloon query:
(echo '{"execute":"qmp_capabilities"}'; sleep 0.5; echo '{"execute":"query-balloon"}') \
| socat - UNIX-CONNECT:/var/run/qemu-server/<vmid>.qmpInside a Linux guest, dmesg should show free page reporting if the kernel supports it:
dmesg | grep -i 'free page\|balloon'virtio-mem is currently QMP/CLI-only; there is no PVE web UI control yet. Grow/shrink live by setting requested-size on the vmem0 object. Avoid combining active ballooning and virtio-mem for the same VM unless specifically testing QEMU behaviour; the two mechanisms can fight over the same memory.
The UI extension is injected into PVE's index.html.tpl and adds microVM-specific affordances. Test it after package upgrades because PVE frontend internals can change.
Minimum checks:
- PVE web UI loads without JavaScript errors.
- Create µVM wizard opens.
- The wizard-generated command matches supported
pve-microvm-templateflags. - Machine type dropdown accepts
microvm. - xterm.js console opens for serial console guests.
When adding wizard fields, also add matching CLI flags or post-create qm set commands. Do not let the GUI generate unsupported arguments.
If a microVM appears to have lost its filesystem after a reboot:
- Check whether the VM started as standard PC instead of microVM:
PID=$(pgrep -f "kvm.*-id <vmid>")
tr '\0' '\n' < /proc/$PID/cmdline | grep -A1 '^-machine$'
tr '\0' '\n' < /proc/$PID/cmdline | grep -E 'scsi-hd|virtio-blk'- Check whether
machine: microvmis still present:
grep '^machine:' /etc/pve/qemu-server/<vmid>.conf- Restore it if missing:
qm set <vmid> --machine microvm
pve-microvm-patch apply
systemctl restart pvedaemon
qm stop <vmid>; qm start <vmid>The data is usually intact; the guest simply saw the root disk as a different device path.
When behaviour changes, update docs in the same commit or immediately after:
README.mdfor quick-start, feature list, and roadmap.docs/installation.mdfor install/update process.docs/configuration.mdfor VM config options.docs/architecture.mdfor command-line/device model changes.docs/known-issues.mdordocs/troubleshooting.mdfor sharp edges and recovery steps.
Keep docs version-agnostic where possible. If a feature depends on a minimum release, state it in prose rather than embedding stale package filenames.