Short answer
To run Docker in a Proxmox LXC container, keep the container unprivileged and enable
the two container features nesting and keyctl, then restart the container:
pct set <vmid> --features nesting=1,keyctl=1
pct reboot <vmid>
In the web UI the same switches are under the container's Options → Features.
Both options are documented in the official Proxmox VE manual, chapter
Linux Container
(pct features).
That is the whole fix for most setups. The rest of this guide explains what the two flags actually do, when privileged containers make sense (rarely), which Docker storage driver you end up with on ZFS or ext4, the errors you are most likely to hit and how to fix them, and how to create Docker-ready containers without editing config files at all.
What nesting and keyctl do
nesting=1 allows containers inside the container. Docker needs it to set up
its own namespaces, mount /proc and /sys for its containers and load its
AppArmor profile. The Proxmox manual notes that nesting exposes parts of the host's procfs and sysfs to
the guest, and that it is best used together with an unprivileged container.
keyctl=1 applies to unprivileged containers only. It allows the
keyctl() system call, which unprivileged containers otherwise see as non-existent. The Proxmox
manual lists it as required for Docker inside a container, because the container runtime uses the kernel
keyring when it starts each Docker container.
Both are stored in the container's config file /etc/pve/lxc/<vmid>.conf as one line:
features: keyctl=1,nesting=1
You can edit that line by hand, but pct set or the web UI is safer because Proxmox validates
the value. Feature changes apply when the container starts, so a reboot of the container (not the host) is
required.
Creating a Docker-ready LXC from scratch
On the Proxmox host, download a template (for example Debian 12) with pveam, then create the
container with the features set from the start:
pveam update
pveam available --section system | grep debian-12
pveam download local debian-12-standard_12.7-1_amd64.tar.zst # use the exact name from the list
pct create 120 local:vztmpl/debian-12-standard_12.7-1_amd64.tar.zst \
--hostname docker-01 --unprivileged 1 --features nesting=1,keyctl=1 \
--cores 2 --memory 2048 --rootfs local-lvm:16 \
--net0 name=eth0,bridge=vmbr0,ip=dhcp --password
pct start 120
pct enter 120
Inside the container, install Docker. The distribution package is the quickest path; Docker's own repository gives you the latest release:
apt update && apt install -y docker.io
docker run --rm hello-world
docker info | grep -i "storage driver"
If hello-world prints its greeting, Docker works. The last command tells you which storage
driver Docker picked, which matters for performance (see below).
Unprivileged vs privileged: which one for Docker?
An unprivileged container maps its users to a high, unused range of IDs on the host (by default root inside the container is UID 100000 outside). If something breaks out of the container, it lands as a nobody on the host. This is the default when you create a container in the Proxmox web UI, and it is the right choice for Docker.
A privileged container runs root as real host root, protected mainly by AppArmor and dropped capabilities. Docker sometimes "just works" there with fewer adjustments, which is why old forum threads recommend it. The price is isolation: a container escape means root on your Proxmox host. Use a privileged container only for workloads you fully trust, and never for anything customers or the internet can reach.
One more honest point: the Proxmox documentation recommends running application containers such as Docker inside a QEMU virtual machine rather than an LXC container. A VM has its own kernel, so Docker behaves exactly as on bare metal, and kernel or AppArmor changes on the host cannot break it. LXC is lighter and faster to boot. For a homelab or internal services, Docker in an unprivileged LXC is a common and workable choice; for production workloads or untrusted images, a small VM is the safer default. Our LXC vs QEMU on Proxmox comparison covers the trade-off in detail.
Storage driver: overlay2 on ext4, LVM-thin and ZFS
Docker's preferred storage driver is overlay2. Whether it works inside an unprivileged
container depends on the filesystem under the container's root disk:
-
ext4 on LVM-thin or a directory storage: current Proxmox kernels allow overlayfs
inside unprivileged containers, so Docker uses
overlay2. -
ZFS: overlayfs on ZFS datasets needs OpenZFS 2.2 or newer, which ships with Proxmox
VE 8.1 and later. On older releases Docker silently falls back to
vfs.
vfs works everywhere but copies every image layer in full: images take many times more
space and container starts are slow. If docker info shows vfs, the clean fix is
to update Proxmox VE. If that is not an option, enable the fuse feature on the container
(pct set <vmid> --features nesting=1,keyctl=1,fuse=1), install
fuse-overlayfs in the container and set it as the driver in
/etc/docker/daemon.json:
{
"storage-driver": "fuse-overlayfs"
}
Then restart Docker with systemctl restart docker and check docker info again.
Common errors and how to fix them
Docker containers fail to start with AppArmor or "permission denied" errors on /proc
Typical when nesting is off. Docker cannot set up the mounts and profile it needs for its
containers. Fix: pct set <vmid> --features nesting=1,keyctl=1 and restart the
container.
Errors mentioning keyctl or the session keyring
The container is unprivileged but keyctl is not enabled, so the runtime cannot create its
session key. Enable keyctl=1 and restart the container.
Feature changes have no effect
Features are applied at container start. pct reboot <vmid> (or stop and start) after
every change. Check the active config with pct config <vmid>.
Disk fills up fast, image pulls are slow
Docker is using the vfs driver. See the storage driver section above.
It worked until a host update
Docker in LXC depends on the host kernel, AppArmor and LXC versions together, so an update on either side can occasionally break it until the next fix lands. Read the Proxmox release notes before updating hosts that run Docker containers, keep a snapshot of the container before upgrading Docker inside it, and move critical Docker workloads to a VM.
Checklist: Docker in an unprivileged LXC
- The container is unprivileged (
pct config <vmid>showsunprivileged: 1). features: keyctl=1,nesting=1is set, and the container was restarted afterwards.docker run --rm hello-worldsucceeds inside the container.docker inforeportsoverlay2(orfuse-overlayfs), notvfs.- The root disk is large enough for your images; Docker data lives in
/var/lib/dockeron the container's rootfs. - The container is included in a backup job. Snapshots are fine for quick rollbacks, but Docker volumes need a real backup like any other data (see Proxmox snapshot vs backup).
- Ports published with
-pare reachable on the container's IP. If the container sits on a private bridge, forward them on the host (see Proxmox NAT and port forwarding).
Doing it in NexoVirt
NexoVirt's create wizard has the container options as plain switches: Unprivileged,
Nesting and keyctl (plus FUSE and allowed mount types when you need
them). Turn them on while creating the container and it comes up Docker-ready, no
pct set, no editing /etc/pve/lxc/*.conf, no reboot afterwards. The same options
are available in the REST API, so a script or a billing system can create Docker-ready containers too:
POST /api/v1/hosts/{hostId}/guests
{
"type": "lxc", "node": "pve1", "vmid": 107, "name": "docker-01",
"cores": 2, "memory_mb": 2048, "disk_gb": 20,
"storage": "local-lvm", "bridge": "vmbr0", "lxc_template_id": 3,
"password": "change-me",
"unprivileged": true, "nesting": true, "keyctl": true
}
The container then shows up with its IP, live usage and an in-browser console like every other guest, across all your Proxmox hosts. For the full API, see the REST API reference.