Docker in Proxmox LXC: nesting + keyctl (pct set command)

  • By NexoVirt Team
  • Updated
  • 8 min read

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

  1. The container is unprivileged (pct config <vmid> shows unprivileged: 1).
  2. features: keyctl=1,nesting=1 is set, and the container was restarted afterwards.
  3. docker run --rm hello-world succeeds inside the container.
  4. docker info reports overlay2 (or fuse-overlayfs), not vfs.
  5. The root disk is large enough for your images; Docker data lives in /var/lib/docker on the container's rootfs.
  6. 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).
  7. Ports published with -p are 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.

NexoVirt Team

Guides and articles from the team building NexoVirt, the self-hosted control panel for Proxmox VE.

Questions about this guide

How do I enable nesting and keyctl for a Proxmox LXC container?

Run pct set <vmid> --features nesting=1,keyctl=1 on the Proxmox host and restart the container (pct reboot <vmid>). In the web UI the same switches are under the container's Options, Features. The setting is stored as features: keyctl=1,nesting=1 in /etc/pve/lxc/<vmid>.conf.

Why does Docker in LXC need nesting and keyctl?

Nesting lets Docker create its own containers inside the LXC container (namespaces, proc and sys mounts, AppArmor profile). keyctl allows the keyctl() system call, which unprivileged containers otherwise see as non-existent and which the container runtime uses when starting each Docker container. The Proxmox manual lists keyctl as required for Docker in an unprivileged container.

Should Docker run in a privileged or unprivileged LXC container?

Unprivileged. Root inside an unprivileged container is an unprivileged user on the host, so an escape does far less damage. Privileged containers run root as real host root and should be reserved for fully trusted workloads. For production or untrusted images, the Proxmox documentation recommends running Docker in a QEMU VM instead.

Why does Docker use the vfs storage driver in my Proxmox LXC?

overlay2 could not be used on the container's filesystem, most often ZFS before OpenZFS 2.2 (Proxmox VE 8.0 and older). vfs copies every layer in full and wastes space. Update Proxmox VE, or enable the fuse feature and switch Docker to fuse-overlayfs in /etc/docker/daemon.json.

Can NexoVirt create Docker-ready LXC containers?

Yes. The create wizard has switches for unprivileged, nesting and keyctl, and the REST API accepts unprivileged, nesting and keyctl fields, so the container is Docker-ready from its first boot without editing config files.

Start running NexoVirt today

Free for homelabs with unlimited VMs on up to 2 nodes. Running a hosting business? Upgrade in place when you grow, no migration.