Docker Inside LXC on Proxmox: Setup, Tuning & Performance
Learn how to run Docker inside unprivileged LXC containers on Proxmox VE 9+ with fuse-overlayfs, OCI templates, and kernel tweaks for near-native performance.
On this page
Running Docker inside LXC containers on Proxmox is one of those setups that quietly saves you gigabytes of RAM and CPU cycles — but only if you configure it correctly. This guide walks through privileged vs unprivileged container choices, OCI template deployment in Proxmox VE 9+, and the exact host-level tweaks needed so your Docker daemons behave themselves without leaking into the hypervisor.
Key Takeaways
- OCI templates let you deploy any image directly as an LXC — no VM boot required.
- Unprivileged LXCs with
fuse-overlayfsare safe by default, but Docker requires either a privileged container or specific host kernel flags. - Docker inside VMs gives clean isolation at the cost of ~10–25% more RAM and slower startup compared to LXC.
- Proxmox VE 9+ ships with
pctOCI support, making it trivial to pull images from Docker Hub or any registry.
How Does Docker Behave Inside an Unprivileged Container?
The short answer: not great out of the box. An unprivileged container maps its root user (uid/gid 0) to a high host range — usually starting around 100000. Docker, however, expects to own /var/lib/docker and create cgroups with uid 0. When you try docker run hello-world in an unprivileged LXC without additional configuration, you'll often see errors about device-mapper or permission denied on the socket.
The fix is straightforward: either run your container as privileged (simplest, best for homelabs), or keep it unprivileged and add a few host-level kernel parameters so Docker's overlay storage works correctly.
Privileged Containers: The Easy Path
Privileged LXC containers get direct access to the host's cgroup filesystem and device nodes — exactly what Docker was built expecting. In Proxmox VE, you set this at container creation or modify an existing one:
pct create 101 local:vztmpl/ubuntu-24.04-default_20250318_amd64.tar.zst \
--ostype ubuntu \
--arch amd64 \
--memory 1024 \
--cores 2 \
--unprivileged=0 \
--features nesting=1,cgroup=nesting,hostid=inherit,nfs=allow
The key flags here are --unprivileged=0 (which makes it privileged), and nesting=1, which allows nested cgroups — Docker needs this to create its own cgroup hierarchy. The cgroup=nesting feature is especially important for newer overlay storage drivers.
Once created, enter the container with:
pct enter 101
Inside, install Docker normally:
apt-get update && apt-get install -y docker.io
systemctl start docker
docker run --rm hello-world
If it prints "Hello from Docker!" without complaint, you're golden. You can verify the cgroup structure looks right with cat /proc/1/cgrouppath — for a privileged container running systemd, this should show /init.scope, not something like /user.slice/user-0.slice.
Unprivileged Containers: The Lean Path
Unprivileged containers are more secure because they run as an untrusted user on the host. They isolate against root exploits and work nicely with Access a Linux VM on Proxmox from Windows via RDP when you need GUI tools running inside your homelab — but Docker needs help.
To make it work, add these to /etc/pve/lxc/101.conf (replace 101 with your container ID):
lxc.apparmor.profile: unconfined
lxc.cgroup.devices.allow: a
lxc.mount.entry: /dev/fuse dev/fuse none bind,optional,create=file
lxc.rootfs.options: mountopt=ro
And on the host itself, ensure your kernel supports FUSE-based overlay storage. On Debian 12/13 (which Proxmox VE is based on):
modprobe fuse
echo "fuse" >> /etc/modules-load.d/fuse.conf
You'll also want to make sure fuse-overlayfs is installed inside the container:
apt-get install -y fuse-overlayfs
Docker will then use it as its default storage driver instead of trying (and failing) with device-mapper. You can verify which driver Docker picked up by running:
docker info | grep "Storage Driver"
# Should show something like "fuse-overlayfs" or "overlay2"
There's a small gotcha here worth mentioning — if you're using the Proxmox VE 9+ OCI templates, they ship with runc as the default runtime. Older versions of Docker (<25) sometimes have issues with runc and nested namespaces in unprivileged containers. Upgrade to at least Docker 26.x for smooth operation:
curl -fsSL https://get.docker.com | sh
docker --version
# Should show something like "Docker version 27.x"
Deploying OCI Templates Directly with pct
Starting in Proxmox VE 9, the container deployment model shifted toward OCI images. This means you can pull any Docker image and run it as a lightweight LXC without building a VM first — which is particularly useful when you're building a private cloud at home and want to minimize overhead.
To deploy an OCI template:
pct create 102 docker.io/library/alpine:3.19 \
--ostype alpine \
--arch amd64 \
--memory 512 \
--cores 1 \
--unprivileged=1
The container will boot in about three seconds on a typical system, using roughly 80 MB of RAM at idle. Compare that to running the same workload inside a virtual machine — you're looking at an additional 3–5 GB for the guest OS and hypervisor overhead. This is why many homelab operators prefer Docker-in-LXC over Docker-in-VM setups when they need to run dozens of lightweight services like Jellyfin, Nextcloud, or Portainer.
For a deeper dive into managing multiple container workloads from one interface, check out Cockpit on Proxmox: Manage KVM, LXC, and Docker in One UI — it gives you a unified view across all your LXCs without needing to SSH into each one individually.
Comparing Docker-in-LXC vs Docker-in-VM on Proxmox
The real question is when to use which approach. The table below compares the two setups for typical homelab workloads:
| Metric | Docker in LXC (privileged) | Docker in VM |
|---|---|---|
| RAM at idle | ~150 MB (container + Docker daemon) | 3–6 GB (guest OS overhead) |
| Disk I/O latency | Near-native (~2 ms typical) | Slightly higher (~4–8 ms via virtio) |
| Network performance | Host network namespace, no NAT | Virtual MAC/virtio-net, minimal CPU cost |
| Startup time | 3–5 seconds | 10–30 seconds depending on guest OS |
| Isolation from host | Moderate (shared kernel) | Strong (separate kernel in VM) |
| GPU passthrough support | Good with --features gpu=1 and driver mounts |
Excellent with full PCI Passthrough |
For most homelab use cases — Home Assistant, Jellyfin on Proxmox LXC, Nextcloud, Pi-hole, and similar services — Docker-in-LXC wins. For workloads that need kernel-level isolation (like running multiple versions of the same service or needing direct access to /dev/dri for transcoding), VMs make more sense.
If you're building a homelab from scratch and want a complete guide on converting an old laptop into a Proxmox VE home server, that article walks through storage, networking, and LXC setup before diving into container workloads like Docker-in-LXC.
Performance Tuning for Docker in LXC
Once your containers are running, there are a few tuning knobs worth adjusting to get the best performance out of them.
First, set up proper cgroup limits so one runaway container doesn't starve the others:
pct set 101 --memory 2048 --cpus 2 --features cpulimit=2
The cpulimit feature ensures your container respects its CPU quota even when Docker spawns multiple containers inside it. Without this, a busy Docker service can consume more than its fair share of host CPU time — especially noticeable if you're running multiple Docker LXCs vs one Docker VM and see inconsistent performance across workloads.
Second, tune the kernel's network stack for containerized traffic. Add these to /etc/sysctl.d/99-docker-lxc.conf:
net.ipv4.ip_forward=1
net.ipv6.conf.all.forwarding=1
net.core.somaxconn=65535
net.ipv4.tcp_max_syn_backlog=8192
vm.swappiness=10
Apply with sysctl -p /etc/sysctl.d/99-docker-lxc.conf. These settings help when you're running several containers simultaneously — the connection backlog values prevent SYN floods during container startup bursts.
Third, if your storage is ZFS (and most Proxmox users are), make sure Docker's overlay driver plays nicely with it:
pct set 101 --features mount=overlay2,zfs
This tells the LXC to use native ZFS mounts rather than falling back to bind mounts, which can significantly reduce I/O latency for container workloads.
Backing Up Docker Containers on Proxmox
One thing many people overlook is that backing up Docker-in-LXC containers works exactly like backing up any other LXC — through Automated Backups with Proxmox Backup Server. Since the container's root filesystem lives in a ZFS dataset, you get snapshot-level backups at no extra cost.
To add a backup job for your Docker containers:
pvesr setup --storagename pbs \
--server pbs.example.com \
--username root@pam \
--password-file /etc/pve/priv/pbs-password.txt
qm create 103 --name docker-vm-backup --memory 2048 --net0 virtio,bridge=vmbr0
For LXC containers specifically:
pvesm set pbs --content rootdir --prune "keep-last=7"
This keeps seven daily snapshots of your container's filesystem. If you're running Docker inside a VM instead, the same PBS system backs up the entire disk image — but at higher storage cost since it captures every byte rather than just changed blocks within ZFS datasets.
When to Choose LXC Over VMS for Docker Workloads
The decision really comes down to workload characteristics. If your services are stateless or have their data on external volumes (NFS, iSCSI via iSCSI Block Storage on Proxmox from TrueNAS SCALE), then LXCs shine because they share the host kernel and start instantly. For stateful workloads that benefit from VM-level isolation — like running multiple isolated Kubernetes clusters with k3s or needing full control over networking stacks — VMs are worth the overhead.
The LXC vs KVM on Proxmox: Measured Resource Overhead article has some excellent benchmarks if you want hard numbers, but the practical takeaway is this: for homelab-scale deployments (under 50 containers), LXCs will serve you better than VMs in almost every metric except isolation.
A Real-World Gotcha You'll Hit
Here's something I learned after spending an afternoon debugging — if you're using Docker-in-LXC with fuse-overlayfs and your host kernel gets updated (as it does during a Proxmox VE 9+ release), the FUSE module might not load automatically on boot. This means your containers will silently fail to start their Docker daemons after a reboot, throwing "cannot create /var/lib/docker" errors that are surprisingly hard to trace back to a missing kernel module.
The fix is simple: add fuse to /etc/modules-load.d/fuse.conf and run update-initramfs -u so it loads early in the boot process. I now check this as part of my post-update routine, alongside verifying that CrowdSec on Proxmox hasn't been accidentally reloaded with new rules during the upgrade.
Conclusion
Running Docker inside LXC containers on Proxmox gives you near-native performance at a fraction of VM overhead — but only if you get your container configuration right from the start. Privileged LXCs are the simplest route for most users, while unprivileged containers with fuse-overlayfs offer better security without sacrificing much in terms of Docker compatibility. With Proxmox VE 9+ OCI templates and a few kernel tweaks, you can deploy dozens of lightweight container workloads that collectively use less RAM than a single VM — and start up orders of magnitude faster.
The next step is to set up automated backups for your containers using Proxmox Backup Server so your Docker-in-LXC services survive reboots and host failures without requiring manual intervention or external storage mounts.