Proxmox LXC vs VM memory: what the number in the box actually means
My Plex container has memory: 2048 in its config. When I typed that number I assumed I had just handed Plex 2 GB of my server’s 32. I had not. On a container that number is a limit. On a VM the same box means something else.
Short version for the skimmers. Proxmox LXC memory allocation is a ceiling, not a reservation. The container uses what its processes and their file cache use, and the host keeps the rest. A VM’s memory is RAM the guest kernel treats as its own, and Proxmox only claws some back through ballooning when the host passes 80% use. On current Proxmox, container swap is a separate limit, not memory plus swap.
KEY FACTS
Proxmox’s defaults for a new container are 512 MB of memory and 512 MB of swap. The memory floor is 16 MB.
My Plex container’s 2048 MB becomes a cgroup memory.max of 2,147,483,648 bytes. Its 512 MB of swap becomes a separate memory.swap.max of 536,870,912 bytes.
Proxmox VE has used pure cgroup v2 by default since 7.0, and since 9.0 it is the only mode.
VM ballooning targets 80% host RAM use. Below that, Proxmox grows guests toward their maximum. Above it, it shrinks them toward their minimum.
Proxmox’s rule of thumb is to leave 1 GB of RAM to the host when you size VMs.
How does Proxmox LXC memory allocation work?
It sets a cap through the kernel’s cgroup memory controller. Proxmox’s container docs say it in one line: container memory “is controlled using the cgroup memory controller.” There is no second kernel and no block of RAM set aside. The container’s processes run on the host kernel like any other process, and the kernel counts what they use against the cap.
So a container set to 2048 MB that is using 300 MB costs the host 300 MB. The other 1.7 GB stays free for everything else. That is why the container summary in Proxmox usually shows a number well under the allocation, which I covered from the other side in the LXC vs VM decision post.
The part that surprised me is what counts. The kernel’s cgroup v2 documentation says the memory controller tracks page cache as well as anonymous memory, plus kernel structures like dentries and inodes and socket buffers. Page cache is the kernel keeping recently read files in RAM. For a Plex container that reads big media files, that cache is charged to the container. It is reclaimable, so the kernel drops it before it hurts anything, but it is why a quiet media container can look busier than its processes are.
What happens when a container hits its limit?
The kernel first tries to reclaim memory inside that container, which mostly means dropping its file cache and pushing anonymous pages to swap if it has any swap allowance. If it cannot get back under the line, the cgroup documentation is blunt: “the OOM killer is invoked in the cgroup.” A process inside the container gets killed. The host and the other guests are not touched.
That is the trade. A VM that runs out of memory deals with it inside its own kernel. A container that runs out loses a process to an OOM kill that the host kernel carries out. For Plex that would mean the server process dying mid stream, so the limit is worth setting with some headroom rather than tightly. You can see whether it has happened from the host:
cat /sys/fs/cgroup/lxc/104/memory.events
The oom_kill line counts kills since the container started. Swap 104 for your container ID.
Why container swap is no longer memory plus swap
Proxmox’s own container wiki still describes swap as a value that gets added to memory, “set to the sum of both value (memory + swap).” That was true under cgroup v1. It is not how the cgroup v2 code writes it. The pve-devel patch that added cgroup v2 support writes the two as independent limits (trimmed to the lines that matter):
my $memory = $conf->{memory} || 512;
my $swap = $conf->{swap} // 0;
$raw .= "lxc.cgroup2.memory.max = $lxcmem\n";
$raw .= "lxc.cgroup2.memory.swap.max = $lxcswap\n";
So on any Proxmox VE 7.0 or later host, which is every host still getting updates, my Plex container gets 2048 MB of RAM and, separately, up to 512 MB of swap. It does not get 2048 MB total with 512 of it allowed to be swap. The difference matters when you size a container and expect the swap to come out of the same budget.
Here is how CT 104’s config turns into kernel limits.
| Config line | cgroup v2 file | Value in bytes | What it caps |
|---|---|---|---|
memory: 2048 |
memory.max | 2,147,483,648 | RAM, including page cache |
swap: 512 |
memory.swap.max | 536,870,912 | Swap, on its own |
Check what yours actually got on the host, not inside the container:
cat /sys/fs/cgroup/lxc/104/memory.max
cat /sys/fs/cgroup/lxc/104/memory.swap.max
cat /sys/fs/cgroup/lxc/104/memory.current
Why does free inside the container disagree with other tools?
Because the container sees its limit through one door and not the other. Proxmox runs LXCFS, a small FUSE filesystem that rewrites files like /proc/meminfo and /proc/swaps so they reflect the container rather than the host. That is why free -m inside CT 104 reports a total near 2048 MB instead of 32 GB.
The cgroup files are a different story. A user on the Proxmox forum showed that /sys/fs/cgroup/memory.max read from inside a container returns max, while the same container’s file on the host reads 4294967296. Java read the inside value and sized its heap off the host’s RAM. No Proxmox staff answered the thread, so treat it as an observation, not a ruling. The practical point holds either way: anything that sizes itself from cgroup files, like a JVM, may think it has the whole host. Set its heap explicitly.
Plex is not Java, so this has not bitten my container. If you run something like a Minecraft server in an LXC, it is the first thing to check.
How is VM memory different?
A VM gets a block of RAM that its own kernel manages. Inside the guest, that memory is simply the machine’s RAM, and the guest fills it with its own cache the way any OS does. Proxmox has two ways to set it, described in its VM docs.
Fixed allocation is memory and minimum memory set to the same number. Proxmox “will simply allocate what you specify.” Automatic allocation is a minimum lower than the maximum. Then Proxmox guarantees the minimum, and while host RAM use is below a target that “defaults to 80%”, it adds memory to the guest up to the maximum. A balloon driver inside the guest grabs or releases pages to make that work. Linux guests have the driver built in. A Windows guest needs the VirtIO balloon driver and its service, blnsvr.exe, installed before any of it does anything.
| LXC container | VM | |
|---|---|---|
| What the number means | A ceiling (cgroup memory.max) | RAM the guest kernel owns |
| Unused memory | Stays with the host | Held by the guest unless the balloon gives it back |
| Swap | Separate per-container limit, host swap | Inside the guest, its own swap |
| File cache | Charged to the container, reclaimable | Lives inside the guest’s RAM |
| Over the limit | OOM kill inside the container | Guest kernel handles it |
| Proxmox default | 512 MB memory, 512 MB swap | Ballooning device on, shares 1000 |
| Sharing pages | Same kernel, same page cache | KSM can merge identical pages, on by default |
The KSM row is the one VMs win on. Kernel Samepage Merging scans for identical memory pages across guests and keeps one copy. Each VM allows it unless you turn allow-ksm off. It helps most when several VMs run the same OS, which is not my setup, with one Windows VM and three containers.
Where does an integrated GPU’s memory come from?
From system RAM. An Intel iGPU like my HD 4600 has no memory of its own. In a VM, using it means passing the whole device through, which needs IOMMU, and my Haswell board does not have VT-d. In a container, the container talks to the host’s /dev/dri devices directly, which is how my Plex container uses Quick Sync. I have not measured how much of that GPU memory, if any, lands on the container’s cgroup bill, so I am not going to guess at a number here.
How I size container memory on a 32 GB host
I size containers for their peak, not their idle, because the penalty for being wrong is a killed process. Then I let the fact that containers do not reserve anything do the rest. CT 104 got 2048 MB and 512 MB of swap, about 6% of the host, and Plex’s own guidance says 4 GB is typically plenty and Linux often needs less. The full build is in my Plex LXC walkthrough.
The thing that actually competes with containers on my host is not other containers. It is the ZFS read cache, which can grow to a large share of RAM on a host where ZFS was added after install. That is covered in the ZFS pool layout post. Check the ARC limit before you shrink a container to make room.
A short checklist that has served me:
- Set the limit at the service’s busiest moment plus a margin, not at what it idles at.
- Give containers a little swap so a spike gets slow instead of killed.
- Read
memory.eventson the host after a week. Anyoom_killabove zero means the limit is too tight. - For VMs, leave ballooning on and leave 1 GB to the host, per Proxmox.
- For anything Java, set the heap by hand inside a container.
If you are still planning the whole box, the home server setup guide has the RAM budget table I started from.
FAQ
Does a Proxmox container reserve the RAM I give it?
No. The memory setting is a cgroup ceiling. The container uses what its processes and file cache use, and the rest stays with the host. A container set to 2048 MB that is using 300 MB costs the host 300 MB.
What happens when an LXC container runs out of memory?
The kernel reclaims what it can inside the container, mostly file cache and swap. If usage still cannot get under the limit, the OOM killer runs inside that container’s cgroup and kills a process there. The host and other guests are not affected.
Does container swap add to the memory limit?
Not on Proxmox VE 7.0 or later. Under cgroup v2, Proxmox writes memory to memory.max and swap to memory.swap.max as two independent limits. The old memory plus swap behaviour was cgroup v1.
Why does a Java app in my container think it has all the host’s RAM?
LXCFS makes /proc/meminfo show the container’s limit, so free looks right. But /sys/fs/cgroup/memory.max read from inside a container has been reported to say max, and the JVM reads that. Set the heap size explicitly.
Should I turn off ballooning on a Proxmox VM?
Proxmox says to leave it enabled in general. With minimum memory below maximum, it guarantees the minimum and grows the guest while host RAM use is under 80%. Windows needs the VirtIO balloon driver and the blnsvr service installed.
The short version
On a container, the memory number is a limit the kernel enforces, and unused memory stays with the host. On a VM, it is RAM the guest owns, with ballooning as the way back. Container swap is its own limit on any current Proxmox. Read the real values from /sys/fs/cgroup/lxc/<ID>/ on the host, and set Java heaps by hand.
The plain text prompt I used to plan this server’s RAM budget with Claude is here: the Proxmox home server prompt.
Stay in the loop.
No spam, just signal.
I send one email when I publish something worth reading. Stock deep dives, new projects, tech breakdowns, and what I'm learning from building in public.
Free forever. Unsubscribe anytime. I respect your inbox.