Proxmox LXC vs VM: Which One Should You Use?
LXC containers or KVM virtual machines on Proxmox VE? A practical guide to overhead, isolation, privileged vs unprivileged, and when passthrough forces a VM.
On a Proxmox VE node, the choice comes down to four rules:
- Default to an LXC container for a Linux service you own and control: reverse proxy, database, Nextcloud, game server, media server.
- Use a VM when the guest is not Linux, needs its own kernel, or needs a PCIe device handed to it exclusively.
- Isolation is the tiebreaker. A VM is a real security boundary. A container is a set of kernel namespaces with guardrails bolted on.
- Run containers unprivileged. Privileged containers belong to the host’s trust domain, and Proxmox’s own documentation says as much.
The LXC-versus-VM question usually gets argued on performance, which is the least interesting axis. CPU throughput is close to native either way. The differences that actually decide it are memory accounting, what a device node can and cannot do, and what happens when someone finds a kernel bug.
What Proxmox is actually giving you
A VM on Proxmox is a QEMU process accelerated by KVM. The guest boots its own kernel off a virtual disk, talks to virtio (or emulated) devices, and has no idea what the host is running. Its definition lives in /etc/pve/qemu-server/<vmid>.conf.
An LXC container is a system container. It shares the host’s kernel but runs a complete distro userland: its own init, package manager and users, persistent across reboots. Its definition lives in /etc/pve/lxc/<vmid>.conf, and its root filesystem is a ZFS subvolume, an LVM-thin volume or a raw image depending on the storage you picked. Templates come from the appliance manager (pveam).
Worth saying plainly because it trips people up: an LXC container is not a Docker container. Docker packages one application and treats the filesystem as disposable; LXC gives you something that behaves like a small Linux server. The web UI’s “CT” button is the second thing, not the first.
Overhead: it is memory, not CPU
With KVM, steady-state compute in a VM lands close enough to bare metal that it will not decide anything, and a container’s processes are just host processes under a cgroup limit. Look at memory instead.
- VM memory is largely spent. Assign 4 GB and the guest kernel plus its page cache will fill it and keep it. Ballooning and KSM claw some back, but plan as though allocated equals consumed.
- Container memory is a ceiling, not a reservation. A container capped at 4 GB that is using 400 MB leaves the rest available to the node. This is the single biggest practical difference. Twelve containers with 4 GB ceilings on a 32 GB host is routine; twelve VMs with 4 GB each is not.
- Page cache is not duplicated. Containers read through the host page cache directly, so one cached copy serves the node. A VM keeps its own page cache inside the guest on top of whatever the storage layer caches, so on ZFS the same blocks can sit in the guest and in ARC at the same time. Proxmox defaults VM disks to
cache=none, which bypasses the host page cache, so this is an ARC or writeback-cache effect rather than plain host double-buffering. - Disk growth is simpler.
pct resizegrows a container’s root or mount point from the host side and the filesystem follows; growing a VM disk withqm resizestill leaves you extending the partition and filesystem inside the guest. Neither direction shrinks, though: Proxmox documents shrinking disk size as unsupported for both. The Proxmox ZFS storage guide covers how each layout behaves. - Startup is near-instant. A container skips firmware, bootloader and kernel boot entirely, so it comes up as fast as its init does. That matters when a node reboots and thirty services need to come back.
One gotcha: because processes inside a container are host processes, naive tooling can report the host’s totals rather than the container’s limits. Proxmox mounts lxcfs to present container-aware /proc entries, which fixes most tools but not all. If a JVM or database inside a container sizes its heap off “available RAM”, set that limit explicitly.
The isolation boundary, which is the real decision
A VM’s boundary is hardware-assisted virtualization. The guest kernel drives virtual hardware, and escaping means finding a bug in KVM or in QEMU’s device emulation. That surface is narrow, heavily scrutinized, and rarely broken in practice.
A container’s boundary is one shared kernel. Every syscall made inside the container is executed by the host kernel, so a local privilege escalation bug in that kernel is a candidate container escape. Proxmox stacks real mitigations on top (user namespaces, AppArmor profiles, seccomp filtering, cgroup device restrictions), and unprivileged containers raise the bar a long way. It is still defense in depth around a shared kernel rather than a separate machine.
So the rule that actually matters:
- Workloads you packaged or trust, running code you control: LXC is fine.
- Anything executing untrusted or user-supplied code (CI runners, shared shells, sandboxes): VM.
- Anything you would describe as multi-tenant: VM.
Do not let benchmark numbers talk you out of that. A VM’s overhead is a known, bounded cost. A container escape is not.
Privileged vs unprivileged containers
Unprivileged containers use user namespaces: UID 0 inside the container maps to a high, unprivileged UID on the host (the default mapping starts at 100000, defined in /etc/subuid and /etc/subgid). Root inside the container is a nobody outside it. This is what the Proxmox GUI creates by default, and it is what you should be running.
Privileged containers skip that mapping. Root inside is root on the host, and only capability drops, AppArmor and seccomp stand between the two. The Proxmox documentation is blunt about what that means: the LXC team considers this kind of container unsafe and will not treat new container escape exploits as security issues worthy of a CVE, so privileged containers should only be used in trusted environments. Treat one as an extension of the host.
Unprivileged costs you two conveniences, both fixable:
- Bind-mount ownership shifts. A host directory owned by UID 1000 shows up inside the container as
nobody, because the container’s UID 1000 is really host UID 101000. Fix it by chowning on the host side to the shifted ID, or by adding a custom idmap to the container config. Do not “fix” it by going privileged. - Some capabilities need feature flags. Nesting, FUSE, keyctl and in-container mounts are opt-in
featuresflags, and apart from nesting, changing them is restricted toroot@pam, precisely because each gives back part of what the unprivileged mapping took away. Enable the specific flag you need rather than the whole privileged mode. NFS and CIFS are the exception worth avoiding entirely: mounting them inside an unprivileged container runs into kernel restrictions even with the flag set, so mount the share on the host and bind-mount it in.
The privileged flag is also not a toggle you flip on an existing container. The practical path is a vzdump backup restored into a new container with the other setting, so decide before you build on top of it.
When passthrough forces a VM
PCIe passthrough means unbinding a device from the host driver and handing the guest direct control of it through the IOMMU. That requires a guest with its own kernel and its own driver for the hardware. A container has neither, and no configuration flag changes that. So these are VM-only:
- Exclusive GPU assignment, for a Windows guest, a gaming VM or a dedicated ML box. See Proxmox GPU passthrough for the IOMMU groups and VFIO binding side of it.
- HBA or disk controller passthrough for a storage appliance guest that wants raw disks, the usual reason to weigh Proxmox against TrueNAS.
- NIC passthrough or SR-IOV virtual functions, typically for a firewall appliance.
Here is the nuance that gets missed: device access is not passthrough. If you want shared use of a device rather than exclusive ownership, a container is usually the easier answer. Hardware transcoding on an integrated GPU, a USB Zigbee stick, a serial adapter: the host driver stays loaded, you expose the device node into the container (current Proxmox does this with dev0:-style device entries in the container config, which work for unprivileged containers too; older setups did the same with raw lxc.cgroup2.devices.allow and lxc.mount.entry lines), fix the group ownership, and it works. Two containers can share one render node. Two VMs cannot share one passed-through GPU.
Pick on exclusive versus shared, not on “GPU means VM”.
Other things that quietly force a VM
- Non-Linux guests. Windows, any BSD, the BSD-based firewall distributions. A container borrows the host’s Linux kernel, so there is nothing to discuss.
- Appliance images that ship their own OS. Home Assistant OS, NAS appliances, most vendor images. They expect to own the disk and the boot process.
- Anything kernel-level. Containers cannot load modules; they inherit whatever the host has. A workload needing its own module, a different kernel version, or nested KVM inside the guest is a VM.
- Very old distro templates. Modern Proxmox uses the unified cgroup hierarchy, and templates built around init systems that predate it will not boot cleanly.
- Docker and Kubernetes. Docker inside LXC does work with the nesting feature enabled, but you are stacking two container runtimes and inheriting the host kernel’s storage-driver quirks. Proxmox’s own guidance is to put Docker in a VM, and that is the right default for anything you would be annoyed to debug at 2am.
Operational differences that bite later
Live migration is VMs only. A running VM can move between cluster nodes without dropping. Containers cannot: Proxmox performs a restart migration, stopping the container on one node and starting it on the other. If a service must survive node maintenance without a restart, that is a VM decision made at build time. It also shapes cluster design and what high availability buys you, since HA restarts either guest type but only VMs migrate gracefully.
Host kernel updates take every container with them. Containers share the host kernel, so a kernel upgrade plus reboot is a full outage for all of them, while VMs on a cluster can be evacuated first.
Snapshots differ in what they capture. On capable storage (ZFS, LVM-thin, Ceph) both guest types snapshot. A VM snapshot can include RAM state so you resume exactly where you were; a container snapshot is filesystem-only.
Backups differ in granularity. vzdump handles both, but containers back up as a file-level archive while VMs back up as disk images. Pulling one config file out of a container backup is trivial; doing it from a VM image is not. Factor that into your backup and restore strategy and Proxmox Backup Server setup.
Networking is identical. Both attach to a Linux bridge and both are policed by the Proxmox firewall at the guest interface, so bridges and VLANs and firewall rules apply unchanged.
The short version
Start with a container. Switch to a VM when any one of these is true:
- The guest is not Linux.
- It needs a PCIe device to itself.
- It needs its own kernel, a kernel module, or nested virtualization.
- It runs untrusted code, or belongs to someone else.
- It must live-migrate for maintenance.
- It ships as an appliance image.
If none of those apply, an unprivileged container will use less memory, start faster, back up more granularly and resize more easily. That covers most of what a homelab or small production node actually runs.
Next steps
- Sizing a node for the mix you end up with: Proxmox hardware requirements.
- Stamping out VMs repeatably: Proxmox cloud-init templates.
- When a guest misbehaves: common Proxmox problems and fixes.
Related
Proxmox VE Hardware Requirements: Minimum vs Recommended
Proxmox VE hardware requirements: real minimum vs recommended specs, why ECC matters less than on a NAS, VT-x/VT-d flags, and boot SSD endurance.
Proxmox VE vs TrueNAS SCALE: Which Should Be the Host?
Proxmox VE vs TrueNAS SCALE: which one belongs on bare metal, when to run TrueNAS as a Proxmox VM, and why HBA passthrough is non-negotiable.
How to Install Proxmox VE: Step-by-Step Install Guide
Install Proxmox VE from ISO to first boot: the installer walkthrough, choosing ext4 vs ZFS, switching to the no-subscription repo, and a first-boot checklist.