From 40 Minutes to 5
Date: December 25, 2025 Challenge: Deploy Gentoo VM faster than making coffee Result: 5-minute deployment using Btrfs snapshots Key Discovery: BIOS mode and Btrfs don’t work together
The Traditional Pain
Deploying Gentoo to a VM the normal way:
- 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)
- Install applications (1-6 hours)
- Configure everything to your preferences (30-60 min)
Total: 6-10 hours minimum. And that’s if nothing breaks.
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 — my full daily driver environment. KDE Plasma, all applications, exact configuration.
Why This Works
Binary Packages
My workstation never compiles. Everything comes pre-built from the binhost server at 10.42.0.194 using -march=x86-64-v2 flags.
Why x86-64-v2?
- ~75-80% of native performance
- Compatible with VMs (QEMU, Proxmox, VMware)
- Works on Intel and AMD
- One compilation, runs everywhere
Btrfs Snapshots
Snapper creates automatic snapshots before and after every emerge. These aren’t just backups — they’re send/receive compatible.
btrfs send /mnt/btrfs-root/@ | ssh proxmox "btrfs receive /mnt/vm-disk"
Block-level transfer. Preserves everything — permissions, ownership, timestamps, extended attributes. Faster than rsync, more complete than tar.
Subvolume Structure
OpenSUSE-style @ subvolumes:
@— Root filesystem@home— User directories@snapshots— Snapshot storage@var-cache— Package cache (excluded from snapshots)
System and data stay separate. Cache doesn’t bloat snapshots.
The Hard Lessons
Lesson 1: Flatten the Snapshot
btrfs receive creates a subdirectory with your snapshot contents:
/mnt/vm-disk/
└── snapshot/
├── etc/
├── usr/
└── ...
You need the files at the root, not in a subdirectory.
Wrong approach:
mv snapshot/* .
This misses hidden files (.bashrc, .config/).
Right approach:
rsync -a snapshot/ . && rm -rf snapshot
The trailing slash on snapshot/ means “contents of,” and rsync handles hidden files correctly.
Cost of learning this: 17 minutes of debugging.
Lesson 2: BIOS Mode and Btrfs Don’t Mix
First attempt: Install GRUB in BIOS mode.
error: filesystem 'btrfs' doesn't support blocklists
Here’s why:
- GRUB in BIOS mode needs fixed disk sectors (“blocklists”)
- Btrfs is copy-on-write — data blocks move during balancing
- Fixed locations + moving blocks = broken bootloader
Solution: EFI boot is mandatory for Btrfs root.
qm set $VMID -efidisk0 $STORAGE:32,efitype=4m,pre-enrolled-keys=1
This cost me an hour of confusion before I understood it was architectural, not configuration.
Lesson 3: UUIDs Change
The VM disk has a different UUID than the source. Forgot to update /etc/fstab on first attempt.
Result: Kernel panic. “VFS: Unable to mount root.”
Now automated:
NEW_UUID=$(blkid $VM_DISK -s UUID -o value)
sed -i "s/UUID=.*/UUID=$NEW_UUID/" /mnt/vm-disk/etc/fstab
Lesson 4: Strip Hardware-Specific Config
Source system has NVIDIA parameters:
nvidia_drm.modeset=1
VMs don’t have NVIDIA GPUs. The kernel looks for hardware that doesn’t exist.
Automated stripping:
sed -i 's/nvidia_drm.modeset=1//g' /mnt/vm-disk/etc/default/grub
The Final Script
# 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
Result: Bootable Gentoo VM in 5-6 minutes.
Use Cases
Testing risky updates: Deploy snapshot to VM, test there first. If it breaks, delete and try again. Main system untouched.
Development environments: Deploy identical VMs for different projects. Full isolation, no container complexity.
Disaster recovery: Not a tar.gz hoping it works — a proven-bootable snapshot you can verify monthly.
Migration: New hardware? Deploy your exact environment in minutes, verify it works, then commit.
The Christmas Gift
December 25, 2025. I wanted to test Nix package manager on Gentoo. Big change — could break things.
With this workflow:
- Create snapshot before Nix
- Install Nix on main system
- If it breaks, rollback instantly
- Or deploy pre-Nix snapshot to VM for comparison
What could have bricked my daily driver became risk-free exploration.
That’s the real gift — the freedom to experiment without fear.
Time Comparison
| Approach | Time |
|---|---|
| Traditional Gentoo install | 6-10 hours |
| Manual deployment (first try, with bugs) | 17 minutes |
| Manual deployment (knowing the steps) | 12 minutes |
| Automated script | 5-6 minutes |
| Just the network transfer | 5 minutes |
The bottleneck is now gigabit ethernet.
Gentoo in 5 minutes. They said it couldn’t be done.