Skip to main content
RunBook Academy

Proxmox VEX · LXC ContainersSecurity

Nesting and Docker inside LXC: what the flag enables and what it costs

Advanced⏱ ~26 minpct

What you'll learn

  • State what features=nesting exposes and what it does not
  • Run Docker in an unprivileged container and verify the storage driver it chose
  • Weigh the security argument for Docker-in-LXC against a VM, on the evidence rather than the folklore
  • Diagnose the two failures: a vfs storage driver eating disk, and a container that will not migrate

Prerequisites

Verified against Proxmox VE 9.2.4 · Proxmox Backup Server 4.2.5 · Ceph Squid / Tentacle · Debian 13 (Trixie) · Linux kernel 7.0 (PVE 9.2 default) · 2026-08-12

Not yet marked complete on this device.

“Can I run Docker in an LXC container?” is the most-asked Proxmox question that has a one-word answer and a one-page explanation. The word is yes. The page is why the official position is that you should not, why thousands of estates do it anyway and are fine, and how to tell which of those two situations you are in.

What nesting actually is

The documentation is terse and precise: nesting exposes procfs and sysfs to allow nested containers.

That is the whole feature. It is not a privilege escalation, a capability grant, or a Docker compatibility mode. It removes two masks.

By default PVE mounts parts of /proc and /sys inside a container as read-only or replaces them with restricted views, because a container has no business reading the host’s kernel state and a great deal of /sys is host state. But a container runtime inside the container - Docker, Podman, systemd-nspawn, or lxc again - needs to read and write those same interfaces to create its own namespaces and cgroups. Without them it cannot start anything.

Read-only / Safethe difference nesting makes, seen from inside
# pct exec 200 -- ls /sys/fs/cgroup/ | head -6
pct exec 200 -- cat /proc/self/cgroup
cgroup.controllers
cgroup.max.depth
cgroup.procs
cgroup.subtree_control
init.scope
system.slice
0::/

Illustrative output

0::/ is the container’s view of its own cgroup root - a nested cgroup namespace. That is what nesting buys, and it is what Docker’s daemon needs to place its containers in cgroups of their own.

Configuration changeenable nesting on an unprivileged container
set -euo pipefail
CTID=210

pct set "$CTID" --features nesting=1
pct reboot "$CTID"

pct config "$CTID" | grep -E '^(unprivileged|features):'

The other feature flags, and what each is for

FlagExposesTypical reason
nesting=1procfs and sysfs, for nested containersDocker, Podman, nested LXC, some systemd units
keyctl=1keyctl syscalls for kernel key managementsystemd-based images that use the kernel keyring; some Docker builds
fuse=1FUSE filesystem supportfuse-overlayfs, sshfs, rclone, s3fs inside the container
mknod=1Device node creationLegacy device-passthrough configs; see the device passthrough lesson
mount=A named list of filesystem types the container may mountA container that must mount NFS or CIFS itself
force_rw_sys=1Read-write access to system filesystemsRare; a compatibility escape hatch

For Docker in an unprivileged container, nesting=1 is the required one and keyctl=1 is frequently needed too, depending on the images being run.

Running Docker in an unprivileged container

It works. Here is the shape of it, and the one check that most people skip.

Configuration changeprepare a container for Docker
set -euo pipefail
CTID=210

pct set "$CTID" --features nesting=1,keyctl=1
pct reboot "$CTID"

pct exec "$CTID" -- apt-get update
pct exec "$CTID" -- apt-get install -y docker.io

pct exec "$CTID" -- docker run --rm hello-world
Read-only / Safethe check people skip - which storage driver did Docker choose?
set -euo pipefail
CTID=210

pct exec "$CTID" -- docker info --format 'Storage Driver: {{.Driver}}'
pct exec "$CTID" -- docker info --format 'Backing Filesystem: {{.DriverStatus}}'
pct exec "$CTID" -- df -h /var/lib/docker

If that reports overlay2, you are in good shape. If it reports vfs, you have a problem that will not announce itself for weeks.

The decision, stated properly

The official Proxmox position is that Docker workloads belong in a VM. The reasoning is sound and it is about support and layering, not about a specific exploit. Here is the decision as it actually presents itself:

SituationAnswerWhy
Third-party or customer-supplied imagesVMYou are not the author of the innermost code. The kernel is shared.
A regulated workload with an isolation requirementVM“Namespaces” will not satisfy an auditor asking about tenant separation.
Docker Compose stack you wrote, on your own clusterLXC with nesting is defensibleTrusted code, and you gain density and instant start
Home lab, single tenantLXC with nesting is fineThe threat model is a household
A CI runner building untrusted pull requestsVM, emphaticallyThis is the worst case: hostile code, by design, on every run
It needs to survive a live migrationVMSee below
Someone said “make it privileged and it works”StopThat is a different and much larger decision

Verification and undo

Read-only / Safea Docker-in-LXC container that is actually healthy
set -euo pipefail
CTID=210

# 1. Privilege level is what you think it is.
pct config "$CTID" | grep -E '^unprivileged:' || echo 'unprivileged: 0 (PRIVILEGED)'

# 2. Only the features you intended are set.
pct config "$CTID" | grep -E '^features:'

# 3. Docker chose a layer-sharing storage driver, not vfs.
pct exec "$CTID" -- docker info --format '{{.Driver}}'

# 4. On-disk usage and Docker's own accounting agree within reason.
pct exec "$CTID" -- du -sh /var/lib/docker
pct exec "$CTID" -- docker system df
Service impact possibleturn nesting back off
set -euo pipefail
CTID=210

pct exec "$CTID" -- systemctl stop docker
pct set "$CTID" --delete features
pct reboot "$CTID"

pct config "$CTID" | grep -E '^features:' || echo 'no features set'

Note what the undo does not do: images and volumes under /var/lib/docker remain on the rootfs, consuming space, with nothing able to read them. If you are decommissioning Docker rather than pausing it, remove them before you remove the flag, while the daemon can still enumerate what it owns.

Knowledge check

Knowledge check · 5 questions

  1. Q1. What does features=nesting actually enable?

  2. Q2. A container running Docker has filled its 32 GiB rootfs. docker system df reports about 2 GiB of images; du on /var/lib/docker reports 26 GiB. What is the most likely explanation?

  3. Q3. Which situations argue for a VM rather than Docker inside an LXC container? Select all that apply.

  4. Q4. Running pct set 210 --features keyctl=1 on a container that already has features: nesting=1 leaves it with keyctl=1 only.

  5. Q5. A Docker container runs as root inside an unprivileged LXC container using the default mapping. What UID is it on the host?

Passing score: 75%. Answers are checked in this browser.