Docker & ContainersII Β· Linux InternalsUser namespaces
User namespaces and rootless Docker
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
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:
# 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""cat /etc/subuid /etc/subgidps -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).
sudo tee /etc/docker/daemon.json <<'EOF'
{
"userns-remap": "default"
}
EOF
sudo systemctl restart dockerdocker info --format 'root={{.DockerRootDir}}{{"\n"}}userns={{json .SecurityOptions}}'
ls -1d /var/lib/docker/*.* 2>/dev/null
getent passwd dockremap; grep '^dockremap:' /etc/subuid /etc/subgidroot=/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:65536Illustrative 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:
| Feature | Behaviour |
|---|---|
--pid=host, --network=host | Rejected. Sharing a host namespace defeats the mapping. |
--privileged | Rejected unless combined with --userns=host. |
| Volume/storage plugins unaware of the mapping | Files land with unmapped ownership; the container sees them as nobody. |
mknod inside a container | Denied. 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
- On shared hosts (CI runners, build hosts, developer laptops): enable rootless.
- On production hosts where you control everything: evaluate rootless vs rootful + userns-remap.
- Always pair user namespaces with capability dropping. Capabilities are useless without them.
- Test that the workload behaves correctly under the mapping. Mount paths, file ownership, signals.
- Document the choice and the rationale.
Knowledge check
Knowledge check Β· 5 questions
Q1. In a rootless Docker setup, what UID runs the dockerd process?
Q2. A container running as UID 0 with user-namespaces enabled has full root on the host.
Q3. Why does bind-mounting `/var/run/docker.sock` break under rootless Docker?
Q4. You add `"userns-remap": "default"` to daemon.json and restart. `docker images` and `docker volume ls` are now both empty. What happened?
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.