The lab didn’t start as a lab. It started as services accreting onto whatever machine happened to be on — a media server here, a script there, everything sharing one filesystem and one fate. The failure that changed it was boring and total: one bad upgrade, and everything went down together, with no isolation between the thing I broke and the things I cared about, and no undo. I rebuilt from scratch, twice.

That’s the problem this lab solves. Not “host services” — break things without breaking everything, and always have a way back. The solution became one Proxmox VE host carrying about twenty LXC containers and a handful of VMs, behind a UniFi network that does the segmentation work. Every post on this blog is now written, built, and pushed from a container in that lab. No seven-node cluster blinking in a rack — the interesting lessons are in how much one box can carry, and where the isolation boundaries actually earn their cost.

One Proxmox host, tiered ZFS pools, and a VLAN-segmented network Proxmox host Ultra 9 285K · 96 GB · ZFS NVMe · fast roots + VMs HDD · bulk ~12 TiB raw backup pool receives only ~20 LXC · a few VMs one kernel, many userspaces UniFi gateway · switch · AP segmentation by trust a dozen VLANs · 10.10.x.0/24 each mgmt IoT · quarantined servers identity guest sandbox Tailscale mesh VPN in CF Tunnel outbound-only publish

zero port-forwards

The host, and the undo button

The box is built around an Intel Core Ultra 9 285K — 24 threads on one socket — with 96 GB of RAM, running Proxmox VE 9.2 on kernel 7.0.14-4-pve. Proxmox gives me three primitives behind one API: full VMs (QEMU/KVM), system containers (LXC), and ZFS storage. At a typical moment the host sits under 1% CPU with ~18% of memory committed — the first surprise the lab taught me is that the constraint on a homelab server is almost never compute. It’s RAM and storage layout.

Storage is where the two rebuilds paid off. Everything is ZFS, split by what the disks are good at: a fast NVMe pool for container roots and VM disks, a big spinning pool (~12 TiB raw between them) for bulk data, and a separate pool that exists only to receive backups. Each container’s root is its own dataset (the blog’s workspace lives on a subvol under the fast pool), which buys the three things I actually use daily:

  • Snapshots before anything risky. Instantaneous, and rollback is one command. The “no undo” failure that started this lab can’t happen anymore — and it has saved me twice since.
  • Per-dataset quotas instead of pre-carved disk images — the pool is shared and containers take what they’re allotted.
  • Checksums on every block. ZFS detects the bit-rot that a plain RAID mirror silently propagates — the distinction I unpacked in RAID: what it does and doesn’t.

The VM/container line is a sizing lesson

I wrote about what a container really is — namespaces plus cgroups, no guest kernel. What the lab taught me is that this definition becomes a budget. My first instinct was VMs for everything, because VMs feel like “proper” isolation. Then I did the arithmetic: a VM pins its allocation — give it 8 GB and that memory is spoken for, plus a whole guest kernel. Twenty services as VMs would have blown through 96 GB before doing any work. An LXC container shares the host kernel; its “8 GB” is a cgroup ceiling, and unused memory stays in the host page cache where it does something useful.

So almost everything is LXC: a reverse proxy, the Cloudflare Tunnel connector, a Tailscale node, a photo library, a vector database, an LLM chat UI, a code-server, a Docker host for compose-shaped things, some monitoring, and the container that runs the coding agent maintaining this site — 4 vCPUs and 8 GB, strange enough to deserve its own post.

The VMs are the exceptions that prove the rule, each needing its own kernel: a Windows 11 guest, an identity/auth appliance I want strongly isolated, and an emulated ARM machine for cross-architecture testing. The tell from inside a container: systemd-detect-virt says lxc, and uname -r shows the host’s -pve kernel. One kernel, many userspaces.

The network does the isolation

The second lesson came from the network side, and it arrived the way these lessons do — as a “wait, that can see this?” moment. A flat network means the cheapest, least-trustworthy device on it (a smart plug, a guest’s phone) sits one hop from the hypervisor’s management interface. Compute isolation is worthless if the network hands out adjacency for free.

So the other half of the lab is a UniFi stack — a UCG Ultra gateway, a PoE switch, and a WiFi 6 access point — with the flat-network problem solved the way the homelab networking post prescribes: segmentation by trust. The management network hangs off 192.168.0.0/24, and everything else lives in its own VLAN and 10.10.x.0/24 — IoT gadgets, guest WiFi, office gear, public-facing servers, NAS traffic, VPN clients, an experiment sandbox for things I don’t trust yet, and an identity/auth segment. A dozen VLANs sounds like enterprise cosplay until the first compromised smart plug can’t see your hypervisor. Then it sounds like the minimum.

SSIDs map onto the same segments — the IoT network is 2.4 GHz-only, which is where small embedded things like my wake-on-LAN gateway live.

Nothing is port-forwarded. Remote access is Tailscale (mesh VPN in, zero inbound holes), and the few deliberately public services go out through a Cloudflare Tunnel — an outbound connection from a container, so the LAN never accepts unsolicited traffic. The blog itself doesn’t touch any of this: it’s static files on Cloudflare’s edge, because a residential connection is exactly where a public website should not live.

There’s also one machine outside the house: an Oracle Cloud free-tier VM running an LLM API proxy — keys stay off the LAN, and it survives local power cuts.

Takeaways

  • The lab exists to learn on. Its real product isn’t uptime — it’s the lessons: isolation boundaries, sizing budgets, and recovery paths you only understand after breaking them.
  • One well-specced Proxmox box covers a remarkable amount of homelab; RAM and storage tiering are the real constraints, not CPU.
  • Prefer LXC over VMs for trusted Linux services — shared-kernel density is the whole game on a single host. Reserve VMs for foreign kernels and hard isolation boundaries.
  • ZFS snapshots before every risky change; quota datasets instead of fixed images; a dedicated backup pool that does nothing else.
  • Segment by trust: VLANs per role, IoT quarantined, management network unreachable from anything untrusted. VPN in, tunnel out, forward nothing.
  • Next in this series: the fleet of bare-metal machines around this box — and what a hypervisor can’t do.