Proxmox hands you two completely different ways to run a workload, LXC containers and full VMs, and the “New CT” and “Create VM” buttons sit right next to each other in the UI like the choice barely matters. It matters. Pick the wrong one for a given workload and you’ll either waste resources you didn’t need to waste, or hit a wall six months in when something a container fundamentally can’t do turns out to be exactly what you needed. Here’s how to actually decide, workload by workload, instead of defaulting to whichever one you set up first.
The core difference, in practical terms
A VM gets its own virtualized hardware and boots its own kernel. It doesn’t know or care what’s running on the host, it’s isolated the same way a physical machine would be. An LXC container shares the host’s kernel and gets isolated at the process/namespace level instead, more like a very well-fenced-off set of processes than a separate computer.
That one architectural difference cascades into everything else:
- Resource overhead. An LXC container has essentially none, no second kernel to boot, no virtualized RAM/CPU/disk layer to pay tax on. A VM always costs you some overhead just to exist, even idling.
- Boot time. Containers start in a second or two. VMs go through an actual boot sequence, BIOS/UEFI, kernel init, the works, so multiple seconds at minimum.
- Kernel dependency. Containers use the Proxmox host’s kernel, full stop. You cannot run a container needing a different kernel version or a different OS family (no Windows containers, no BSD containers) than what the host is running. A VM can run literally any OS the hypervisor supports, kernel version and OS family are completely decoupled from the host.
- Isolation strength. A VM’s isolation is hardware-enforced, a kernel exploit inside a VM doesn’t get you access to the host or sibling VMs without also breaking out of the hypervisor itself, a much harder bar. A container’s isolation is namespace-based, weaker by design, a serious enough kernel vulnerability could theoretically let a process inside a container reach the host, since they share the exact same kernel.
Neither is “better”, they’re built for different jobs, and most functioning homelabs run a mix of both rather than picking one exclusively.
Where LXC is the right call
Lightweight services with no unusual kernel needs. Reverse proxies, DNS servers (Pi-hole/AdGuard), monitoring stacks, Git servers, wikis, most single-purpose Linux daemons, this is LXC’s home turf. You get near-native performance, instant boot, and you can run a dozen of these on hardware that would visibly struggle running a dozen equivalent VMs.
Density matters and the workload is trusted. If you’re trying to pack a lot of small services onto modest hardware (the classic mini-PC homelab situation), LXC’s near-zero overhead is the difference between “runs comfortably” and “constantly fighting for RAM.” This assumes the workloads are things you trust, your own self-hosted apps, not untrusted or internet-facing code you’d want hard isolation from.
Fast iteration and disposability. Spinning a container up and tearing it down to test something takes seconds, which matters if you’re doing that constantly. VMs work for this too, just slower at every step.
Privileged vs unprivileged, and why it matters. Proxmox lets you create containers as privileged (container root = host root, effectively) or unprivileged (container root is mapped to an unprivileged host UID via a UID shift). Default to unprivileged unless you have a specific reason not to, it’s the meaningfully safer option and closes most of the “container breakout” concern above. Privileged containers are sometimes genuinely necessary (some Docker-in-LXC setups, certain hardware passthrough scenarios, NAS-backed bind mounts where the storage backend can’t preserve unprivileged UID mappings) but that should be a deliberate exception, not the default, and worth documenting why when you do it.
Where a VM is the right call
Anything that needs its own kernel or a different OS. Windows, BSD, a specific Linux kernel version your workload depends on, any of that rules out LXC entirely, no debate to have.
Docker and container-orchestration hosts. Running Docker (or Kubernetes) inside an LXC container is possible and plenty of homelabbers do it, but it comes with real friction, cgroup/AppArmor quirks that need workarounds (nesting flags, sometimes network_mode: host to dodge sysctl permission issues under confinement), and occasional breakage when the container runtime assumes capabilities an LXC guest doesn’t fully provide. If you’re standing up a Docker host as a primary workload rather than one throwaway test, a small VM running Docker natively is usually less troubleshooting over the life of that host than fighting LXC’s constraints indefinitely. If you already have a working Docker-in-LXC setup and it’s stable, that’s a fine reason to leave it, just know it’s the fussier path if you’re starting fresh.
Untrusted or internet-facing workloads where isolation strength actually matters. If something is reachable from the public internet, or you’re running code you don’t fully trust, a VM’s hardware-enforced isolation is worth the overhead. This is the security tradeoff from the section above made concrete: the blast radius of a compromised VM is smaller and better contained than a compromised container sharing your host’s kernel.
Anything doing GPU passthrough, complex PCIe passthrough, or needing a full virtualized hardware stack. LXC can do limited device passthrough for simple cases, but full GPU passthrough (for a gaming VM, an AI/ML workload, a Plex hardware-transcode setup that needs exclusive device access) is squarely VM territory, built on IOMMU/VFIO, and not something LXC’s architecture supports the same way.
Snapshotting an entire OS state you want to roll back cleanly, including kernel-level state. Both LXC and VMs support snapshots in Proxmox, but a VM snapshot captures a genuinely complete, isolated machine state. For anything where you want to be absolutely certain a rollback restores everything with no shared-kernel edge cases, VM snapshots are the more bulletproof option.
A decision shortcut
If you’re staring at the create button and not sure, ask two questions in order:
- Does it need a different kernel, a non-Linux OS, GPU passthrough, or genuinely hard security isolation? If yes, VM, no further discussion needed.
- Is it a lightweight, trusted, Linux-native service where you want density and fast boot? If yes, LXC, and default to unprivileged unless you hit a specific wall that requires privileged.
Everything in between, Docker hosts being the most common gray area, comes down to how much you value the marginal resource savings of LXC against the reduced friction of a VM running Docker the way it was actually designed to run. There’s no universally correct answer there, it’s a tradeoff specific to your hardware headroom and your tolerance for occasionally debugging container-runtime-inside-container quirks.
Bottom line
Most homelabs end up running both, LXC for the pile of lightweight always-on services where density and boot speed matter, VMs for anything needing a different kernel, real hardware isolation, GPU access, or a Docker host you don’t want to fight with. Don’t pick one type and force every workload into it. Match the container type to what the workload actually needs, and the “which one do I use” question mostly answers itself.