Skip to main content
RunBook Academy

Docker & ContainersII Β· Linux InternalsUser namespaces

User namespaces and rootless Docker

Advanced⏱ ~26 min

What you'll learn

  • Explain how user namespaces map UIDs inside the container to UIDs on the host
  • Configure rootless Docker with subuid/subgid mappings
  • Identify workloads that may break under rootless and decide whether to use it

Prerequisites

Verified against Docker Engine 29.x Β· Docker Engine 28.x Β· Docker Compose 2.x Β· containerd 2.x Β· runc 1.2.x Β· BuildKit 0.20+ Β· Linux kernel 5.15+ Β· Ubuntu 24.04 LTS Β· Debian 12 (Bookworm) Β· 2026-08-09

Not yet marked complete on this device.

The most dangerous statement in container security is β€œthe container runs as root”. Inside the container’s PID namespace, the process looks like UID 0. On the host filesystem, the same process is whatever UID the host maps it to. User namespaces are the mechanism that makes this mapping happen.

The mapping

flowchart LR
  subgraph Container["Inside the container"]
    C0[UID 0 root]
    C1[UID 1000 alice]
  end
  subgraph Host["On the host"]
    H0[UID 100000 unprivileged]
    H1[UID 101000 unprivileged]
  end
  C0 -. mapped via uid_map .-> H0
  C1 -. mapped via uid_map .-> H1

A user namespace remaps UIDs. Inside the namespace, the process sees the conventional 0–65535 UID space. On the host, the same process is some higher UID range. The kernel performs the mapping on every filesystem operation.

Default Linux user-namespace configuration in Docker (modern versions):

  • The container’s UID 0 maps to a host UID in the dockremap/configured range (default: 100000–165535).
  • The container’s UID 1000 maps to a host UID 100000 higher.

A process inside the container that thinks it is writing /etc/nginx/nginx.conf as root is actually writing to a host file that the host UID 100000 can write. If the file’s host UID is 0 (root-owned) and host mode is 600, the container cannot modify it.

Rootless Docker

Rootless Docker runs the entire Docker daemon as a non-root user. Combined with user namespaces for the daemon’s containers, the daemon has no path to host root even if the daemon itself is compromised.

To enable:

Configuration changerootless setup
# One-time setup
dockerd-rootless-setuptool.sh check
dockerd-rootless-setuptool.sh install
# Run as a systemd user service
systemctl --user enable docker
systemctl --user start docker
# Verify
echo "DOCKER_HOST=unix:///run/user/$(id -u)/docker.sock""
Configuration changesubuid/subgid
cat /etc/subuid /etc/subgid
Read-only / Safeverify rootless
ps -o user,pid,comm -p $(pgrep -f dockerd-rootless)

Mapping without going full rootless

You can use user namespaces on a rootful daemon by setting --userns-remap=default in daemon.json. The daemon then maps container UID 0 to the host UID range configured for the dockremap user (the default user Docker creates for this purpose).

Service impact possibleuserns-remap
sudo tee /etc/docker/daemon.json <<'EOF'
{
"userns-remap": "default"
}
EOF
sudo systemctl restart docker
Read-only / Safewhere is the daemon actually storing data?
docker info --format 'root={{.DockerRootDir}}{{"\n"}}userns={{json .SecurityOptions}}'
ls -1d /var/lib/docker/*.* 2>/dev/null
getent passwd dockremap; grep '^dockremap:' /etc/subuid /etc/subgid
root=/var/lib/docker/231072.231072
userns=["name=seccomp,profile=builtin","name=userns"]
/var/lib/docker/231072.231072
dockremap:x:998:998::/home/dockremap:/usr/sbin/nologin
/etc/subuid:dockremap:231072:65536
/etc/subgid:dockremap:231072:65536

Illustrative output

name=userns in SecurityOptions is the positive confirmation that remapping is active. On a host without it, DockerRootDir is plain /var/lib/docker and name=userns is absent β€” those two facts together distinguish β€œremapping is on” from β€œthe data is gone” in under a second.

What stops working under remapping

The Docker documentation lists these as incompatible with user namespaces, and they fail at docker run time rather than silently:

FeatureBehaviour
--pid=host, --network=hostRejected. Sharing a host namespace defeats the mapping.
--privilegedRejected unless combined with --userns=host.
Volume/storage plugins unaware of the mappingFiles land with unmapped ownership; the container sees them as nobody.
mknod inside a containerDenied. Device creation needs real host root.

--userns=host on an individual container opts it out of the mapping while the rest of the host keeps it. That is the escape hatch for the one legacy workload that cannot be remapped β€” and it is also a hole, so it belongs in a change record with a name against it, not in a script.

What to do in production

  1. On shared hosts (CI runners, build hosts, developer laptops): enable rootless.
  2. On production hosts where you control everything: evaluate rootless vs rootful + userns-remap.
  3. Always pair user namespaces with capability dropping. Capabilities are useless without them.
  4. Test that the workload behaves correctly under the mapping. Mount paths, file ownership, signals.
  5. Document the choice and the rationale.

Knowledge check

Knowledge check Β· 5 questions

  1. Q1. In a rootless Docker setup, what UID runs the dockerd process?

  2. Q2. A container running as UID 0 with user-namespaces enabled has full root on the host.

  3. Q3. Why does bind-mounting `/var/run/docker.sock` break under rootless Docker?

  4. Q4. You add `"userns-remap": "default"` to daemon.json and restart. `docker images` and `docker volume ls` are now both empty. What happened?

  5. Q5. Which of these are rejected outright once userns-remap is enabled? Select all that apply.

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