Christmas morning, 2025. I wanted to test the Nix package manager on my Gentoo daily driver. Big change — the kind that could break things in ways you don’t discover until three days later when audio stops working and your boot time triples.
Six months ago, that would have meant either accepting the risk or spending a weekend setting up a test environment. Today it meant deploying a complete Gentoo VM — my full desktop environment, every application, exact configuration — in five minutes. Then testing Nix there. If it breaks, delete the VM. Main system untouched.
That’s the Christmas gift: the freedom to experiment without fear.
But getting here cost some blood.
The Traditional Pain
Deploying Gentoo to a VM the normal way, for the uninitiated:
- Create VM in Proxmox (5 min)
- Install stage3 tarball (10 min)
- Configure portage, make.conf (15 min)
- Compile kernel (30-60 min)
- Install bootloader (10 min)
- Configure services, network, users (15 min)
- Install desktop environment (2-4 hours of compilation)
- Install applications (1-6 hours depending on your masochism level)
- Configure everything to match your preferences (30-60 min)
Total: 6-10 hours minimum. And that’s if nothing breaks. On Gentoo, something always breaks. A USE flag conflict during the desktop environment emerge. A missing kernel option that you don’t discover until you try to boot. A circular dependency that portage helpfully describes with a wall of text that would make a compiler blush.
I ran through this process enough times to memorize it. And every time I thought: there has to be a faster way.
There was. It just required rethinking the whole approach.
The Argo OS Way
- Create VM in Proxmox (30 seconds — automated)
- Send Btrfs snapshot to VM disk (5 minutes over gigabit)
- Auto-configure fstab, GRUB, network (30 seconds)
- Start VM (10 seconds)
Total: 6 minutes including network transfer.
Not a minimal install. Not a base system with no GUI. My full daily driver environment. KDE Plasma, all applications, every dotfile, exact configuration. The same system I use every day, replicated to a VM in the time it takes to make coffee. Badly.
The trick is two things working together: binary packages and Btrfs snapshots.
Binary Packages: Why My Workstation Never Compiles
Gentoo’s whole identity is compiling from source. USE flags, -march=native, optimized-for-your-hardware binaries. It’s the selling point and the tax.
My workstation doesn’t pay that tax anymore. Everything comes pre-built from the binhost server at 10.42.0.194 — the Tau-Beta machine that exists solely to compile packages continuously.
# On the workstation - /etc/portage/make.conf
PORTAGE_BINHOST="http://10.42.0.194"
FEATURES="${FEATURES} getbinpkg"
The binhost compiles with -march=x86-64-v2 flags. Not native. Deliberately.
Why x86-64-v2 instead of native? Because native binaries only run on the exact CPU that compiled them. x86-64-v2 runs on anything recent — VMs under QEMU/Proxmox/VMware, Intel, AMD, whatever. You sacrifice maybe 20-25% of native performance in exchange for one compilation that runs everywhere. For a workstation running KDE and Firefox, that difference is invisible.
The Tau-Beta compiles. The workstation consumes. The VMs consume. One build, many targets.
Btrfs Snapshots: Not Just Backups
Snapper creates automatic snapshots before and after every emerge. Most people think of these as backups — something you restore when an update breaks things. They are. But they’re also send/receive compatible, which makes them something much more powerful.
btrfs send /mnt/btrfs-root/@ | ssh proxmox "btrfs receive /mnt/vm-disk"
That’s a block-level transfer. Not file-by-file like rsync. Not a compressed archive like tar. Block-level. It preserves everything — permissions, ownership, timestamps, extended attributes, SELinux labels, ACLs. The destination is a byte-for-byte replica of the source at the moment the snapshot was taken.
Over gigabit ethernet, a full system transfer takes about 5 minutes. The bottleneck is the network, not the filesystem.
The subvolume structure follows the OpenSUSE-style layout:
@— Root filesystem@home— User directories@snapshots— Snapshot storage@var-cache— Package cache (excluded from snapshots so binary packages don’t bloat them)
System and data separated. Cache isolated. Snapshots stay lean.
The Hard Lessons
Getting here wasn’t a straight line. Every lesson below cost me time. Some of them cost me a lot of time.
Flatten the Snapshot (17 Minutes of Debugging)
btrfs receive creates a subdirectory with your snapshot contents:
/mnt/vm-disk/
+-- snapshot/
|- etc/
|- usr/
+- ...
The VM needs files at the root of the partition, not in a subdirectory. My first instinct:
mv snapshot/* .
This misses hidden files. .bashrc, .config/, .local/ — all left behind in the snapshot/ directory. The VM boots into a system that looks almost right but is subtly broken in ways that take an embarrassingly long time to diagnose.
# The actual fix
rsync -a snapshot/ . && rm -rf snapshot
The trailing slash on snapshot/ means “contents of.” Rsync handles hidden files correctly. 17 minutes of “why is my shell config gone” before I figured this out. Not my finest debugging.
BIOS Mode and Btrfs Don’t Mix (1 Hour of Confusion)
First attempt at installing the bootloader:
error: filesystem 'btrfs' doesn't support blocklists
Spent an hour thinking this was a configuration problem. It’s not. It’s architectural.
GRUB in BIOS mode needs fixed disk sectors — “blocklists” — to find its next stage. Btrfs is copy-on-write. Data blocks move during balancing, defragmentation, any internal maintenance. Fixed locations plus moving blocks equals a bootloader that works until the filesystem rearranges itself, then silently bricks.
EFI boot is mandatory for Btrfs root. Full stop.
qm set $VMID -efidisk0 $STORAGE:32,efitype=4m,pre-enrolled-keys=1
This isn’t a preference or a best practice. BIOS boot on Btrfs is fundamentally broken by design. The hour I spent trying to make it work was an hour of fighting physics.
UUIDs Change (Kernel Panic)
The VM disk has a different UUID than the source. On my first attempt, I forgot to update /etc/fstab.
Result:
VFS: Unable to mount root fs on unknown-block(0,0)
Kernel panic - not syncing: VFS: Unable to mount root fs
A kernel panic because fstab pointed to a UUID that didn’t exist on this disk. The kernel found no root filesystem, panicked, and presented me with a wall of register dumps that weren’t going to help anyone.
Now automated:
NEW_UUID=$(blkid $VM_DISK -s UUID -o value)
sed -i "s/UUID=.*/UUID=$NEW_UUID/" /mnt/vm-disk/etc/fstab
Strip Hardware-Specific Config
Source system has NVIDIA parameters in GRUB:
nvidia_drm.modeset=1
VMs don’t have NVIDIA GPUs. The kernel spends time looking for hardware that doesn’t exist, logging errors about missing devices, and sometimes hanging during initialization.
sed -i 's/nvidia_drm.modeset=1//g' /mnt/vm-disk/etc/default/grub
Small thing. The kind of thing you don’t think about until a VM boot hangs for 30 seconds on “Loading NVIDIA kernel module” and you remember that virtual machines don’t have graphics cards.
The Final Script
After all the lessons, the deployment script looks like this:
# Create VM
qm create 400 --name gentoo-test --memory 8192 --cores 4
# Allocate disks
pvesm alloc tank 400 vm-400-disk-0 50G
qm set 400 --scsi0 tank:vm-400-disk-0
qm set 400 -efidisk0 tank:32,efitype=4m,pre-enrolled-keys=1
# Format
mkfs.btrfs /dev/zvol/tank/vm-400-disk-0
mkfs.vfat -F 32 /dev/zvol/tank/vm-400-disk-1
# Mount and receive
mount /dev/zvol/tank/vm-400-disk-0 /mnt/vm
ssh driver "btrfs send /mnt/btrfs-root/@" | btrfs receive /mnt/vm
cd /mnt/vm && rsync -a snapshot/ . && rm -rf snapshot
# Configure
NEW_UUID=$(blkid /dev/zvol/tank/vm-400-disk-0 -s UUID -o value)
echo "UUID=$NEW_UUID / btrfs defaults,noatime,compress=zstd 0 1" > /mnt/vm/etc/fstab
# Bootloader
mount /dev/zvol/tank/vm-400-disk-1 /mnt/vm/boot/efi
grub-install --target=x86_64-efi \
--boot-directory=/mnt/vm/boot \
--efi-directory=/mnt/vm/boot/efi \
--bootloader-id=Gentoo \
--removable
# Start
umount -R /mnt/vm
qm start 400
Every line in that script has a story. The rsync -a snapshot/ . was 17 minutes. The -efidisk0 was an hour. The UUID sed was a kernel panic. The NVIDIA strip was a 30-second boot hang.
Result: Bootable Gentoo VM in 5-6 minutes. Full desktop. All applications. Exact configuration.
What This Actually Enables
Testing risky updates. Want to try a major version bump? Kernel upgrade? New init system? Deploy a snapshot to a VM, test there first. If it breaks, delete the VM and try a different approach. Main system never at risk.
Development environments. Need isolated environments for different projects? Deploy identical VMs in minutes. Full desktop, full toolchain, complete isolation without container complexity.
Disaster recovery. This isn’t a tar.gz sitting on a NAS hoping it works when you need it. It’s a proven-bootable snapshot that you can verify monthly by actually booting it. The backup is the same mechanism as the deployment.
Migration. New hardware? Deploy your exact environment in minutes, verify everything works, then commit to the move. No “I hope my backup restores correctly” anxiety.
The Numbers
| Approach | Time |
|---|---|
| Traditional Gentoo install | 6-10 hours |
| Manual deployment (first try, with bugs) | ~40 minutes |
| Manual deployment (knowing the steps) | 12 minutes |
| Automated script | 5-6 minutes |
| Just the network transfer | ~5 minutes |
The bottleneck is gigabit ethernet. With 10GbE, the transfer would be under a minute. The rest of the script — VM creation, formatting, configuration, bootloader — takes maybe 45 seconds total.
Gentoo in 5 minutes. My past self, the one who spent a full weekend on his first Gentoo install and emerged @world overnight because the compile took 14 hours, would not believe this. My present self barely believes it and I wrote the script.
The distance between “traditional Gentoo install” and “automated script” is the entire journey. Every step of that journey — from seedbox scripts to ESXi to distributed infrastructure to binary packages to Btrfs snapshots — fed into this moment. The 5-minute deployment isn’t a hack. It’s the product of understanding every layer of the stack well enough to skip the ones that don’t matter.
Merry Christmas. Here’s a bootable Gentoo VM. Took less time than the wrapping paper.