DEV Community

LK Wood IV
LK Wood IV

Posted on Originally published at techfuelhq.com

Proxmox LXC or VM: a per-service table, a decision tree and the gotchas

Adapted from LXC vs VM in Proxmox: When to Use Each, published on TechFuelHQ.

The kernel decides it. A Proxmox VM boots its own kernel on virtual hardware. An LXC container, which Proxmox calls a CT and manages with pct (the Proxmox Container Toolkit), runs on the host's. So a container runs only Linux and can't live-migrate, while a VM runs Windows or FreeBSD as readily as Linux and can take a whole PCI device. The Proxmox behaviour below is from Proxmox VE 9.2 (released May 21, 2026) and its 9.2.12 manual, dated September 18, 2026.

Which one for each service

Service Put it in Why
Docker host (Compose stacks) VM Proxmox's container chapter keeps nesting application containers inside a QEMU VM as a recommended practice for maximum isolation and live migration. Docker in an unprivileged LXC runs with nesting=1,keyctl=1. I'd still put a Compose stack in a VM.
Home Assistant VM running HAOS Home Assistant calls HAOS the recommended installation type for most users. A container install has no apps, and Z-Wave is controlled by an app.
Plex or Jellyfin on an Intel iGPU LXC Pass /dev/dri/renderD128 in as a device with the render group's GID. The driver stays on the host.
Plex or Jellyfin on a dedicated NVIDIA card VM, when one guest gets the card With PCI passthrough the VM owns the card, and the host and other VMs can't use it. Sharing it with an LXC instead needs the NVIDIA kernel driver on the host, since a container runs on the host's kernel.
NAS operating system (TrueNAS) VM with the disk controller passed through For data you care about, TrueNAS says the storage controller must be assigned to the VM via PCI passthrough. TrueNAS lists VM snapshots and live migration among what passthrough gives up; ZFS snapshots inside TrueNAS still work.
File shares from the host's own ZFS pool LXC with a bind mount The host keeps the pool and hands one directory to the container: pct set 100 -mp0 /mnt/bindmounts/shared,mp=/shared. vzdump never backs up a bind mount, so the pool needs its own backup.
Windows guest VM Proxmox's docs: other operating systems like FreeBSD or Windows can't run inside a container.
Firewall (OPNsense, pfSense) VM Both are built on FreeBSD. Netgate says checksum offloading must be disabled on VirtIO interfaces.
DNS filter (Pi-hole, AdGuard Home) LXC A plain Linux service with no module, device or other OS to ask for, and a container's runtime cost is "low, usually negligible".

Anything not listed goes through the tree.

The decision tree

New workload in Proxmox?
│
├─ Docker/Compose stack or Home Assistant OS? → VM
│
├─ Windows, FreeBSD or any non-Linux OS? → VM
│
├─ One guest owns a whole PCI device (GPU, HBA, NIC)? → VM
│
├─ Needs its own kernel or a module the host lacks? → VM
│
├─ Untrusted or public-facing code? → VM
│
├─ Must keep running while it moves between cluster nodes? → VM
│
└─ Everything else → LXC container
   (a Linux service, trusted code; an iGPU render node or a host directory can be passed in)
Enter fullscreen mode Exit fullscreen mode

When a service could go either way, I'd start it in a VM. Moving it into a container later is a planned job; finding out three months in that a container needs the whole GPU is a rebuild.

What the overhead looked like on my box

Measured on a Beelink SEi12 Pro (N150, 16GB RAM, 512GB NVMe) running Proxmox VE 8.x, then the current release. Treat the absolute megabytes as 8.x-era; the container-versus-VM gap is structural, so the shape holds.

Metric LXC (Debian 12, idle) VM (Debian 12, idle, virtio)
RAM used by guest ~55 MB ~280 MB
CPU overhead <1% 1 to 3%
Storage IO vs bare metal ~98% ~94 to 97%
Network throughput vs bare metal ~99% ~97%
Boot time 0.5 to 1.5 seconds 8 to 20 seconds
Backup size (Debian + 1 app) ~400 MB ~2.0 GB

The storage IO gap closes significantly when using virtio-blk with io_uring.

Proxmox-specific gotchas

Unprivileged is the default. The container's root UID 0 is mapped to an unprivileged user on the host, by default in both the Create CT wizard and pct create. The LXC project calls a privileged container unsafe and doesn't treat new escapes from one as worthy of a CVE. Keep privileged containers for trusted work.

NFS or SMB inside a container. The NFS and SMB/CIFS boxes in the container's Features dialog are marked "privileged only", and pct.conf(5) warns that an NFS mount can block the host's I/O completely and prevent it from rebooting. Mount the share on the Proxmox host and bind-mount it into the container. The container stays unprivileged.

Docker inside a container. An unprivileged container needs keyctl=1 and nesting=1. The web interface has turned nesting on for new unprivileged containers since Proxmox VE 7.1. VE 9.1 can create a container straight from an OCI image, but running application containers that way is still a technology preview, and Compose support is on the roadmap, not in 9.2.

Kernel modules. A container runs on whatever modules the host kernel has. pct.conf(5) lists force_rw_sys, fuse, keyctl, mknod, mount and nesting, and none of them loads a module. WireGuard isn't a reason for a VM: it's been in the mainline kernel since Linux 5.6, and Proxmox VE 9.2 ships kernel 7.0. A module the Proxmox kernel doesn't carry, a different ZFS version or an out-of-tree NIC driver is a reason.

Live migration. In a cluster, qm migrate <vmid> <target> --online moves a running VM; passed-through PCI or USB devices block it, local disks don't. A running container can't live-migrate. pct migrate --restart shuts it down (killing it after 180 seconds by default), copies it and starts it on the target.

Snapshots depend on the storage. Container snapshots need ZFS, LVM-thin or Ceph RBD (BTRFS is a technology preview). On directory, NFS, CIFS or plain LVM storage a container gets none. A VM also gets them on file storage when its disk is qcow2.

Backups

A container's snapshot-mode backup needs every backed-up volume on snapshot-capable storage; without it, suspend mode rsyncs to a temporary directory and needs space for the copy. Bind and device mounts are never backed up. A VM's snapshot mode is a live backup on any storage type, and with the guest agent running it calls guest-fsfreeze-freeze and guest-fsfreeze-thaw.

The layout I'd use: the ZFS pool stays on the Proxmox host, datasets bind-mount into unprivileged containers for native Linux services like Jellyfin or a Samba share, and Compose stacks go in a VM. The pool needs its own backup.

Top comments (0)