The Question Every Homelabber Eventually Asks
You've got Proxmox running, maybe a Beelink EQ12 Mini PC (~$189) humming quietly on a shelf, and you want to self-host a bunch of services: Vaultwarden, Nextcloud, Uptime Kuma, Home Assistant, Immich. Then the question hits you: should I use LXC containers or spin up a Docker VM?
This is one of the most-debated topics in the r/homelab and r/selfhosted communities, and for good reason β both approaches work, but they have meaningfully different trade-offs depending on your use case. After running both in production on Proxmox for over two years, here's the honest breakdown.
What LXC Actually Is (and Isn't)
LXC (Linux Containers) is Proxmox's native containerization layer. It's not Docker. LXC containers share the host kernel directly β there's no hypervisor, no emulation, no separate kernel image. Each LXC container gets its own filesystem, network namespace, and process space, but system calls go straight to the Proxmox host kernel.
This makes LXC containers extremely lightweight. A fresh Debian 12 LXC runs at around 30β50 MB of RAM versus a full VM which typically needs 512 MB minimum just to boot. For simple services like a DNS resolver (Pi-hole), a reverse proxy (Nginx Proxy Manager), or a monitoring agent, LXC is phenomenally efficient.
The catch: LXC containers are tied to the host kernel. You can't run a Windows container, you can't use kernel modules that aren't loaded on the host, and running Docker inside an LXC container requires extra configuration (more on that below).
What Docker Is in the Proxmox Context
Docker is an application containerization runtime. In a Proxmox homelab, you typically run Docker in one of two ways:
- Inside a VM β spin up a Debian/Ubuntu VM, install Docker, run your containers there. Clean, fully isolated, works exactly like Docker on any other Linux machine.
- Inside an LXC container β run Docker within a Proxmox LXC by making it privileged (or using fuse-overlayfs for unprivileged). More efficient than a full VM, but with caveats around security and driver support.
Docker's advantage is its massive ecosystem. The Docker Hub has pre-built images for nearly every self-hosted app, and Docker Compose lets you declare multi-service stacks in a single YAML file. If you want to run Immich (which has 5 separate services), Docker Compose is the sane choice.
The Decision Matrix
Here's the framework I use when deciding which approach to take for a new service:
Use LXC when:
- The service has a native Linux package (apt/pacman installable) β Pi-hole, Nginx Proxy Manager, Vaultwarden via the official binary, Home Assistant OS (as a VM, but HA Supervised in an LXC works too)
- You need maximum density β LXC is the most efficient way to run many small services on limited RAM. Upgrading to 32GB DDR4 SODIMM (~$59) on a mini PC lets you run 15β20 LXC containers comfortably
- The service doesn't have complex multi-container dependencies
- You want Proxmox's native backup (vzdump) to snapshot the entire service cleanly
Use Docker (in a VM) when:
- The app is distributed primarily as a Docker image (Immich, Paperless-ngx, Authentik)
- The service requires a multi-container stack via Docker Compose
- You need strict isolation β a VM has a real kernel boundary between it and the host
- The service needs GPU passthrough (Plex transcoding, Ollama with a GPU) β you can pass a GPU to a VM and then let Docker use it
- You're running untrusted or externally-exposed workloads
Use Docker inside LXC when:
- You want Docker's ecosystem without VM overhead
- You're comfortable with the extra configuration steps and security trade-offs
- Your mini PC has 16 GB or less RAM and every MB counts
Setting Up a Service in LXC (Practical Example: Vaultwarden)
Let's walk through deploying Vaultwarden (a Bitwarden-compatible password manager) as an LXC container on Proxmox.
First, create the container via the Proxmox web UI or CLI. I use the Proxmox Helper Scripts project (tteck's scripts) for common services, but here's the manual approach:
# On the Proxmox host
pct create 200 local:vztmpl/debian-12-standard_12.7-1_amd64.tar.zst \
--hostname vaultwarden \
--cores 1 \
--memory 256 \
--rootfs local-lvm:8 \
--net0 name=eth0,bridge=vmbr0,ip=dhcp \
--unprivileged 1 \
--start 1
pct exec 200 -- bash -c "apt update && apt install -y curl"
Then inside the container, install Vaultwarden from the official binary release:
pct enter 200
# Create a service user
useradd -m -s /bin/bash vaultwarden
# Download the latest binary
cd /opt
curl -L https://github.com/dani-garcia/vaultwarden/releases/latest/download/vaultwarden-$(uname -m)-unknown-linux-musl.tar.gz | tar xz
chmod +x vaultwarden
# Create data directory
mkdir -p /opt/vaultwarden-data
# Create a systemd service
cat > /etc/systemd/system/vaultwarden.service << 'EOF'
[Unit]
Description=Vaultwarden Server
After=network.target
[Service]
User=vaultwarden
WorkingDirectory=/opt
Environment=DATA_FOLDER=/opt/vaultwarden-data
Environment=DOMAIN=https://vault.yourdomain.com
Environment=WEBSOCKET_ENABLED=true
ExecStart=/opt/vaultwarden
Restart=on-failure
[Install]
WantedBy=multi-user.target
EOF
systemctl enable --now vaultwarden
Vaultwarden runs with 64β80 MB RAM in this configuration. In a VM, you'd need at least 256β512 MB just for the OS overhead on top of that.
Setting Up Docker in a Proxmox VM
For a multi-container stack, a dedicated Docker VM is the cleanest approach. Here's a minimal Debian 12 VM with Docker:
# Inside your Debian 12 VM
apt update && apt install -y ca-certificates curl gnupg
install -m 0755 -d /etc/apt/keyrings
curl -fsSL https://download.docker.com/linux/debian/gpg | gpg --dearmor -o /etc/apt/keyrings/docker.gpg
chmod a+r /etc/apt/keyrings/docker.gpg
echo "deb [arch=$(dpkg --print-architecture) signed-by=/etc/apt/keyrings/docker.gpg] \
https://download.docker.com/linux/debian $(. /etc/os-release && echo $VERSION_CODENAME) stable" \
| tee /etc/apt/sources.list.d/docker.list
apt update && apt install -y docker-ce docker-ce-cli containerd.io docker-compose-plugin
Then deploy a stack like Immich with a single compose file:
# /opt/immich/docker-compose.yml
services:
immich-server:
image: ghcr.io/immich-app/immich-server:release
volumes:
- /mnt/photos:/usr/src/app/upload
- /etc/localtime:/etc/localtime:ro
env_file: .env
ports:
- "2283:3001"
depends_on:
- redis
- database
restart: always
redis:
image: docker.io/redis:6.2-alpine
restart: always
database:
image: docker.io/tensorchord/pgvecto-rs:pg14-v0.2.0
env_file: .env
volumes:
- pgdata:/var/lib/postgresql/data
restart: always
volumes:
pgdata:
cd /opt/immich && docker compose up -d
Running Docker Inside an LXC Container
If you want Docker's ecosystem without a full VM, you can run Docker inside a privileged LXC. This is a middle ground β more efficient than a VM, but less isolated than one.
In Proxmox, edit the LXC config file:
# On the Proxmox host, edit /etc/pve/lxc/101.conf (replace 101 with your container ID)
# Add these lines:
features: keyctl=1,nesting=1
lxc.apparmor.profile: unconfined
lxc.cap.drop:
Or for an unprivileged container using fuse-overlayfs (more secure but more complex):
# Inside the LXC container
apt install -y fuse-overlayfs
# Edit /etc/docker/daemon.json
cat > /etc/docker/daemon.json << 'EOF'
{
"storage-driver": "fuse-overlayfs"
}
EOF
systemctl restart docker
The fuse-overlayfs approach lets you keep the container unprivileged while still running Docker. Performance is slightly lower than native overlay2, but for typical homelab workloads you won't notice.
Storage Considerations
One often-overlooked factor: storage throughput. Both LXC containers and Docker VMs benefit massively from fast NVMe storage. Running many containers on a spinning HDD creates I/O bottlenecks quickly. A WD Black 2TB SN850X NVMe SSD (~$129) as your Proxmox data store changes the experience entirely β container startups and snapshot operations that took 30 seconds on HDD finish in under 3 seconds.
For networking between containers and the host, adding a TP-Link 2.5G USB Ethernet Adapter (~$22) to your mini PC gives you enough bandwidth to move backups and large files without saturating the link used by your services.
Backups: LXC Has the Edge
Proxmox's built-in backup tool (vzdump) works natively with LXC containers. You can snapshot and restore an entire LXC β including all running services and their data β from the Proxmox web UI in seconds. Scheduling this via the Datacenter β Backup UI takes two minutes to configure.
For Docker VMs, you have two choices: either backup the entire VM (which works but creates large backup files), or mount your Docker volumes on a dedicated storage path and back that up separately with something like restic or borgbackup. The LXC approach is simpler and more space-efficient for most use cases.
Performance Reality Check
In practice, the performance difference between LXC and a Docker VM for typical homelab services (web UIs, databases, media servers) is negligible. Both will feel fast on modern hardware. The meaningful differences are:
- Boot time: LXC containers start in under 1 second. VMs take 10β30 seconds.
- RAM overhead: LXC containers use only what the service needs. VMs need 256β512 MB minimum for the OS.
- Density: On a 16 GB system, you can comfortably run 20+ LXC containers. Running 20 VMs would exhaust RAM quickly.
- Isolation: VMs have a hard kernel boundary. A compromised Docker container in a VM cannot affect the Proxmox host. A compromised privileged LXC container potentially can.
My Personal Setup
For reference, here's how I've organized services across both approaches on a Proxmox node:
- LXC containers: Pi-hole, Nginx Proxy Manager, Vaultwarden, Uptime Kuma, Netdata, Tailscale exit node, Headscale
- Docker VM: Immich, Paperless-ngx, Authentik, Portainer (for managing the other Docker containers)
- Docker in LXC: n8n + Ollama stack (where I needed Docker Compose but wanted to keep RAM usage low)
This hybrid approach gives me the best of both worlds: efficient, native-feeling LXC containers for simple services and Docker's ecosystem for complex multi-service apps.
The Bottom Line
Don't treat this as a binary choice. Use LXC when a service has a clean native Linux install path and you want maximum efficiency. Use Docker (in a VM) when an app is Docker-native, multi-container, or needs strict isolation. Use Docker inside LXC when you're RAM-constrained and willing to do a bit of extra configuration.
Proxmox's strength is precisely that it handles all three approaches natively β you're not locked into one paradigm. Start with LXC for simple services, add a Docker VM for complex stacks, and let the workload dictate the tool.