Docker in Proxmox LXC for Lightweight Production Workloads

Run Docker in Proxmox LXC containers with fuse-overlayfs and cgroup v2 for near-native speed, predictable memory limits, and reliable backups.

Proxmox Pulse Proxmox Pulse
9 min read
A glowing pearl-white capsule floats inside an open gold frame on a marble surface.

Running Docker inside Proxmox LXC containers gives you the low overhead of containerization with the storage and networking flexibility of the Proxmox ecosystem, and with the right setup it handles production workloads just as reliably as a full KVM VM. By using unprivileged containers with the fuse-overlayfs storage driver, you can run Docker Compose stacks, DNS services like Technitium DNS, and backup agents without privileged mode or host kernel tweaks. This guide walks through the exact configuration steps to get native overlay2 performance, reliable bind mounts, and predictable memory usage on Proxmox VE 8.4 and newer.

Key Takeaways

  • Storage Speed: Unprivileged LXC containers with fuse-overlayfs deliver near-native Docker performance without needing --privileged mode.
  • Kernel Sharing: LXC shares the Proxmox host kernel, which saves RAM but means Docker cgroup v2 must be configured correctly to avoid memory limit issues.
  • Template Choice: Using the official Proxmox LXC template with --storage-type lvm-thin or zfs ensures consistent bind mount behavior for Docker volumes.
  • Networking: Docker's built-in bridge networking works out of the box, but for advanced routing you may need to adjust the LXC net0 configuration.
  • Backup Compatibility: LXC containers back up cleanly with Proxmox Backup Server, and Docker volumes can be included in the snapshot without extra configuration.

Why run Docker inside LXC on Proxmox?

Most homelabs and small production setups run Docker either directly on bare metal or inside a KVM VM. While both approaches work, they each carry overhead that LXC avoids. A KVM VM emulates hardware and runs its own kernel, which means you pay for that virtualization layer on every I/O operation. Running Docker directly on the Proxmox host keeps things simple, but you lose the isolation and resource controls that LXC provides.

LXC containers sit between these two extremes. They share the host kernel, so they boot in seconds and use less RAM, yet they get their own filesystem, network stack, and resource limits. For services like Technitium DNS on Proxmox: A Better Pi-hole for Homelabs or a dnsweaver: Automatic DNS for Proxmox, Docker & K8s instance, this means faster restarts and lower memory pressure across the cluster.

When you run Docker inside LXC, you also get the benefits of Proxmox's native management tools. You can snapshot the container alongside its Docker volumes, apply resource limits via the web UI, and migrate it between nodes without stopping the Docker daemon. If you are already running Cloudflare Tunnel on Proxmox for Zero-Trust Remote Access or Cockpit on Proxmox: Manage KVM, LXC, and Docker in One UI, keeping Docker inside LXC fits naturally into that workflow.

How to configure unprivileged LXC containers for Docker

The default LXC container created by Proxmox is unprivileged, meaning the container's root user maps to a non-root user on the host. This is great for security, but Docker inside an unprivileged container can run into permission issues with bind mounts and storage drivers. The fix is straightforward: use fuse-overlayfs as the Docker storage driver and ensure the container has the right capabilities.

First, create a new LXC container with a recent template. The Proxmox VE 8.4 and 9.0 templates include the necessary base packages, but you will still need to install Docker and configure the storage driver.

pct create 201 local:vztmpl/ubuntu-22.04-standard_22.04-1_amd64.tar.zst \
  --hostname docker-lxc \
  --memory 2048 \
  --swap 512 \
  --cores 2 \
  --rootfs local-lvm:8 \
  --net0 name=eth0,bridge=vmbr0,ip=dhcp \
  --unprivileged \
  --features nesting=1

The nesting=1 feature is critical. It allows the container to run its own init system or process manager, which Docker needs to function correctly. Without it, you might see errors like Failed to start Docker Application Container Engine during boot.

Once the container is created, log in and install Docker:

pct exec 201 -- bash -c "apt update && apt install -y docker.io && systemctl enable docker && systemctl start docker"

Now, configure the Docker daemon to use fuse-overlayfs. This driver works natively inside unprivileged containers because it handles user namespace mapping at the filesystem level.

pct exec 201 -- bash -c "cat > /etc/docker/daemon.json << 'EOF'
{
  \"storage-driver\": \"fuse-overlayfs\",
  \"exec-opts\": [\"native.cgroupdriver=systemd\"]
}
EOF"

The native.cgroupdriver=systemd option ensures Docker uses systemd for cgroup management, which avoids conflicts with the LXC host cgroup setup. After making this change, restart Docker:

pct exec 201 -- systemctl restart docker

What storage driver should you use, and how do you set it up?

Docker supports several storage drivers, but the two most common on Proxmox are overlay2 and fuse-overlayfs. The choice depends on whether your LXC container is privileged or unprivileged.

Privileged containers can use overlay2 directly because they have full access to the host kernel's overlay filesystem support. Unprivileged containers, however, lack the necessary capabilities to mount overlay2 efficiently, so fuse-overlayfs is the better choice. It provides the same performance characteristics while working correctly with user namespace mapping.

Here is a quick comparison of the two drivers in the context of Proxmox LXC:

Feature overlay2 (Privileged LXC) fuse-overlayfs (Unprivileged LXC)
Kernel dependency Requires host kernel overlay support Uses FUSE for userspace filesystem
Performance Slightly faster for large files Near-native speed, negligible overhead
User namespace Works best with --privileged Designed for unprivileged containers
Setup complexity Needs --privileged flag Works out of the box with default settings
Volume compatibility Full support for bind mounts Full support with idmap mount
RAM usage Same as host Docker Same as host Docker

For most homelab and production use cases, fuse-overlayfs is the safer choice because it avoids the need to toggle the --privileged flag. If you do choose overlay2, you will need to create the container with --privileged and ensure the host kernel has overlay module loaded:

modprobe overlay
pct create 202 local:vztmpl/debian-12-standard_12.5-1_amd64.tar.zst \
  --hostname docker-ovl \
  --memory 2048 \
  --swap 512 \
  --cores 2 \
  --rootfs local-lvm:8 \
  --net0 name=eth0,bridge=vmbr0,ip=dhcp \
  --privileged

How to run Docker Compose stacks reliably inside LXC

Docker Compose is the standard way to manage multi-container applications, and it works well inside Proxmox LXC containers with a few adjustments. The main challenge is ensuring that bind mounts and volume permissions are consistent across container restarts and host reboots.

Start by creating a Docker Compose file in the container's home directory:

pct exec 201 -- bash -c "mkdir -p /root/myapp && cd /root/myapp && cat > docker-compose.yml << 'EOF'
version: '3.8'
services:
  app:
    image: nginx:alpine
    ports:
      - '8080:80'
    volumes:
      - ./data:/usr/share/nginx/html
    restart: unless-stopped
EOF"

Next, launch the stack and verify that the volume is mounted correctly:

pct exec 201 -- bash -c "cd /root/myapp && docker compose up -d && docker compose ps"

If you see permission errors when writing to the ./data directory, it is usually because the container's UID/GID mapping does not match the host. You can fix this by adding the idmap mount feature to the LXC container configuration:

pct set 201 --features idmap=1
pct reboot 201

The idmap feature ensures that file ownership is preserved across the container boundary, which is especially useful when running services like How Proxmox Backup Server Solves Plex's Backup Problem or Automated Backups with Proxmox Backup Server inside Docker.

A real-world gotcha: cgroup v2 and Docker daemon memory limits

One issue that catches many users off guard is how Docker handles memory limits when running inside an unprivileged LXC container. By default, Docker uses the cgroup v1 driver, which can conflict with the LXC host's cgroup v2 setup. The result is that Docker may not respect the memory limits you set in the Proxmox web UI.

To avoid this, ensure that the Proxmox host is running cgroup v2 and that Docker is configured to use the systemd driver. You can verify the host's cgroup version with:

stat -f -c '%T' /sys/fs/cgroup

If the output is tmpfs, your host is using cgroup v2. If it is devpts, you are still on cgroup v1. Most recent Proxmox VE releases default to cgroup v2, but it is worth checking if you are upgrading from an older version.

Once you confirm cgroup v2, update the Docker daemon configuration as shown earlier, and verify that the limits are applied:

pct exec 201 -- bash -c "docker info | grep -A 5 'Cgroup'"

You should see Cgroup Driver: systemd and Cgroup Version: 2. If the limits are still not respected, you can manually set them in the Proxmox web UI under the container's Resources tab, or by editing the LXC configuration file:

pct set 201 --memory 2048 --swap 512

A practical tradeoff: when LXC Docker isn't the right fit

While running Docker inside LXC offers many benefits, it is not always the best choice. The main tradeoff is kernel flexibility. Because LXC shares the host kernel, you cannot run different kernel versions inside the container, which can be a limitation if you need specific kernel modules for hardware acceleration or networking.

For example, if you are running GPU-accelerated services like Jellyfin on Proxmox LXC with Hardware Transcoding, you may need to pass through the GPU device directly to the LXC container. This works well for most use cases, but if you need kernel-level changes, a KVM VM might be more appropriate.

Another consideration is networking. Docker's built-in bridge networking works out of the box in LXC, but if you need advanced routing or multiple virtual networks, you may need to configure the LXC net0 interface manually. This is less of an issue for simple homelab setups, but it can become a factor in larger deployments.

Finally, consider your backup strategy. LXC containers back up cleanly with Proxmox Backup Server, and Docker volumes are included in the snapshot. However, if you rely heavily on Docker's built-in volume management, you may want to use bind mounts instead of Docker volumes for easier restoration. This is a minor point, but it can simplify your backup workflow significantly.

Conclusion

Running Docker inside Proxmox LXC containers gives you a lightweight, highly isolated environment that boots quickly, uses less RAM, and integrates naturally with Proxmox's native tools. By using unprivileged containers with fuse-overlayfs, configuring cgroup v2 correctly, and leveraging bind mounts for storage, you can run production workloads with confidence. The next step is to migrate one of your existing Docker services, such as a DNS server or backup agent, to an LXC container and monitor its performance over a week. You will likely find that the setup is worth the initial effort, and you can gradually expand to cover the rest of your stack.

Share
Proxmox Pulse

Written by

Proxmox Pulse

Sysadmin-driven guides for getting the most out of Proxmox VE in production and homelab environments.

Related Articles

View all →