On this page
Docker is the key that unlocks the enormous ecosystem of self-hosted apps — most projects ship a docker run command or a compose.yaml as their primary install method, so sooner or later every Proxmox homelab needs a Docker host. But where you run Docker on Proxmox matters more than beginners expect, and it’s a question with a genuinely wrong answer. Get it wrong and a routine host kernel upgrade can take every one of your services down at once — a failure mode that has caught plenty of experienced people off guard.
This guide covers the real decision — LXC vs VM for Docker — the setup for both, and Portainer for a friendly web UI.
The decision: VM, almost always
Here’s the short version, then the reasoning:
| Docker in a VM (recommended) | Docker in an LXC | |
|---|---|---|
| Kernel | Its own — behaves as documented | Shared with host |
| Upgrades | Clean, isolated | Can break on host kernel upgrade |
| Overhead | ~500 MB–1 GB base RAM | Very low |
| Isolation | Strong | Weak (esp. if privileged) |
| Best for | Your real Docker host | One throwaway lightweight app |
The reasoning comes down to one fact: Docker and LXC are both Linux container systems. Each one works by slicing up a shared kernel with namespaces and cgroups, and running Docker inside LXC stacks those two slicing mechanisms on top of each other — something neither was designed for. It does work with the right features enabled, but it’s fragile in a specific, frustrating way: the setup that ran fine for months can break the day Proxmox ships a new host kernel, because the inner Docker layer depended on kernel behavior that just changed underneath it. A VM sidesteps the entire problem. It brings its own kernel, so Docker behaves exactly as its documentation and every tutorial online assume. Unless you’re squeezing a single tiny app onto an already-busy node, use a VM — the half-gigabyte of extra RAM is the cheapest insurance in your lab.
For one lightweight, stateless app on a RAM-constrained node, a nesting-enabled unprivileged LXC running Docker is fine — as long as you’re happy to rebuild it if a Proxmox upgrade breaks it. For everything else, the VM is worth the extra half-gig of RAM.
Task 1: Build the Docker VM (recommended path)
The commands below contain example values to replace with your own: youradmin (your actual login username), container ID 120 and hostname docker01 (any free ID and name in your cluster), the Debian template filename (match what pveam available lists on your node), and local-lvm (your storage name). If a value looks specific to one machine, it’s a placeholder to change — not a literal to copy.
Create a VM following the first-VM walkthrough: Debian 12, 2 vCPU, 4 GB RAM, 32 GB disk, QEMU guest agent enabled. A cloud-init template makes this instant if you have one.
Inside the VM, use Docker’s official convenience script, then verify:
curl -fsSL https://get.docker.com | sh
docker run --rm hello-world
Add your user to the docker group so you don’t need sudo for every command:
usermod -aG docker youradmin
Log out and back in for the group change to take effect.
Task 2: The LXC path (only if you must)
If you’re deliberately going the LXC route, the container needs the nesting and keyctl features:
pct create 120 local:vztmpl/debian-12-standard_12.7-1_amd64.tar.zst \
--hostname docker01 \
--cores 2 --memory 2048 --swap 512 \
--rootfs local-lvm:16 \
--unprivileged 1 --features nesting=1,keyctl=1 \
--net0 name=eth0,bridge=vmbr0,ip=dhcp \
--onboot 1 --password --start 1
Then install Docker inside it exactly as in Task 1. See the Proxmox LXC docs on the feature flags.
Docker-in-LXC can break after a Proxmox host kernel upgrade — storage driver mismatches are the usual culprit. Keep your compose files and volumes backed up so the container is disposable.
Task 3: Add Portainer
Portainer gives you a web UI over your Docker host. Deploy it with a persistent volume:
docker volume create portainer_data
docker run -d \
--name portainer \
--restart=always \
-p 9443:9443 \
-v /var/run/docker.sock:/var/run/docker.sock \
-v portainer_data:/data \
portainer/portainer-ce:latest
Open https://<docker-host-ip>:9443, create the admin account within a few minutes of first launch (a security timeout), and you’re in. From here you can deploy stacks (compose files) straight from the UI.
Rather than :9443 everywhere, put Portainer and your apps behind a reverse proxy with names from Pi-hole local DNS — e.g. https://portainer.homelab.lan.
What’s next
You’ve now got a stable Docker host that will survive Proxmox upgrades, and a UI to manage it without memorizing command-line flags. Two follow-ups are worth doing while the momentum is there: make your services reachable at clean HTTPS URLs with a reverse proxy, and back up the VM itself — your compose files and volumes all live inside it, which means one VM backup protects your entire Docker world.
Related posts:
- Proxmox VM vs LXC: When to Use Each — the decision this guide applies to Docker.
- Creating Your First Proxmox VM — build the VM your Docker host runs in.
- Creating Your First Proxmox LXC Container — the LXC path, if you take it.
- What Is Traefik? Reverse Proxies Explained — expose your Docker apps over HTTPS.
- Pi-hole on Proxmox LXC — local DNS names for your containers.
- Proxmox Backup Server — back up the VM holding your stacks.
- The Docker localhost Trap: 24 Hours of Red, Zero Real Downtime — a networking gotcha waiting inside whichever host you pick.
Sources: Docker documentation, Portainer documentation, Proxmox VE Linux Container documentation.
Comments
Comments are powered by GitHub Discussions — sign in with a GitHub account to join the conversation.