user@argobox:~/journal/2025-12-25-from-40-minutes-to-5
$ cat entry.md

From 40 Minutes to 5

○ NOT REVIEWED

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:

  1. Create VM in Proxmox (5 min)
  2. Install stage3 tarball (10 min)
  3. Configure portage, make.conf (15 min)
  4. Compile kernel (30-60 min)
  5. Install bootloader (10 min)
  6. Configure services, network, users (15 min)
  7. Install desktop environment (2-4 hours)
  8. Install applications (1-6 hours)
  9. Configure everything to your preferences (30-60 min)

Total: 6-10 hours minimum. And that’s if nothing breaks.


The Argo OS Way

  1. Create VM in Proxmox (30 seconds, automated)
  2. Send Btrfs snapshot to VM disk (5 minutes over gigabit)
  3. Auto-configure fstab, GRUB, network (30 seconds)
  4. 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:

  1. Create snapshot before Nix
  2. Install Nix on main system
  3. If it breaks, rollback instantly
  4. 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

ApproachTime
Traditional Gentoo install6-10 hours
Manual deployment (first try, with bugs)17 minutes
Manual deployment (knowing the steps)12 minutes
Automated script5-6 minutes
Just the network transfer5 minutes

The bottleneck is now gigabit ethernet.


Gentoo in 5 minutes. They said it couldn’t be done.