Docker audit fix

How to Configure User Namespace Remapping in Docker

With userns-remap unset, UID 0 inside a container is UID 0 on the host, so a container escape arrives as root. Setting userns-remap to default maps container root onto an unprivileged subordinate UID range from /etc/subuid.

Hiren KalariyaLast reviewed: Oct 6, 2026Check userns-remap
Medium
severity
No
needs root or sudo
3
results it can return
7 of 8
checks in this audit

Every threshold on this page is transcribed from the docker-security-daemon-socket audit script that CtrlOps runs, and a build check fails if the two ever disagree. See all 8 Daemon & Socket checks, or how the audit runs.

Medium severityNo sudo needed

What this check reads

It reads the running dockerd command line from ps -eo args and looks for --userns-remap=<value>; if nothing is found there it falls back to a "userns-remap": "<value>" key in /etc/docker/daemon.json, so the command line takes precedence, as it does for the daemon. A match on its own is not enough, because the value has to be non-empty: "userns-remap": "" is how Docker itself turns remapping off, and it counts here as not configured. A non-empty value is the PASS and everything else is the WARN.

When it applies

Runs whenever the docker binary exists - no daemon access and no root required, since it reads the dockerd command line and /etc/docker/daemon.json. It looks for --userns-remap=<value> or "userns-remap": "<value>" and requires a non-empty value: "userns-remap": "" is Docker's own way of disabling remapping and is correctly treated as not configured. Because it only reads configuration, it reports the daemon's default - it cannot see that an individual container was started with --userns=host, which opts that container out of remapping entirely.

What each result means

Result thresholds this check applies
ResultWhenWhat it means
SKIPDocker not installedNothing was checked. With no docker binary present, neither the command line nor daemon.json was consulted.
PASSConfigured with a non-empty valueThe daemon is set to remap container UIDs, so container root becomes an unprivileged host UID. This is the daemon default only, and a container started with --userns=host still opts out.
WARNNot configuredNo remapping is set, so root inside a container is root on the host the moment a container escapes.

Why it matters

userns-remap maps container UID 0 to an unprivileged host UID range (dockremap via /etc/subuid//etc/subgid). A root process inside the container is an ordinary user outside. CIS Docker 2.9 "Enable user namespace support"; OWASP Docker Cheat Sheet Rule #2; Docker docs "Isolate containers with a user namespace".

Why it fails, and when it is wrong

  • Enabling it changes where images and volumes live (/var/lib/docker/<uid>.<gid>/), so existing containers and images disappear from docker ps/images until re-pulled. Bind-mounted host files owned by root become unreadable inside the container. Containers that need --pid=host, --net=host or --privileged must opt out with --userns=host.
  • Rootless hosts get WARN here even though they have stronger isolation; read alongside Rootless Mode.
  • Kubernetes and some orchestrators manage this differently.

How to fix it

Plan for what disappears

Once remapping is on, images, containers and volumes live under a new directory below /var/lib/docker, so everything that exists today appears to vanish. Note what is running and copy out any named-volume data you need first. Containers that need --privileged, --net=host or --pid=host must opt out with --userns=host.

Set userns-remap in daemon.json

Merge this into the keys already in /etc/docker/daemon.json. With default, Docker creates the dockremap user and uses its subordinate range.

{ "userns-remap": "default" }

Restart Docker and confirm the mapping

sudo systemctl restart docker
grep dockremap /etc/subuid /etc/subgid
docker info --format '{{.SecurityOptions}}'      # expected: includes name=userns

Re-pull images and fix bind-mount ownership

Pull the images again, then give each bind-mounted host directory to the remapped owner: the subordinate UID base plus the UID the process uses inside the container. For container root with a base of 100000, that is:

sudo chown -R 100000:100000 ./data
docker compose pull && docker compose up -d

Verify the fix

ps -eo args | grep '[d]ockerd' | grep -o 'userns-remap[= ][^ ]*'
sudo grep -n userns-remap /etc/docker/daemon.json
docker info --format '{{.SecurityOptions}}'        # expected: includes name=userns

# Prove the mapping is real - container root should be an unprivileged host UID:
docker run --rm -d --name unstest alpine sleep 60
ps -o user=,pid= -p "$(docker inspect -f '{{.State.Pid}}' unstest)"
# expected: a high subordinate UID (e.g. 100000), not root

# The subordinate ranges backing it:
grep dockremap /etc/subuid /etc/subgid

Debugging

The common case. Enable it with {"userns-remap": "default"} in /etc/docker/daemon.json and systemctl restart docker, after reading the trade-offs below.

Expected in three situations: bind-mounted host directories now need ownership matching the remapped subordinate UID range; --privileged, --net=host, --pid=host and --userns=host containers are incompatible or opt out; and existing images/volumes under /var/lib/docker are re-created under a new path, so previously created containers and volumes appear to vanish. Migrate deliberately, not on a live host.

The blind spot: --userns=host on an individual container bypasses the daemon default, and the check only reads daemon configuration. Look for it with docker inspect -f '{{.Name}} {{.HostConfig.UsernsMode}}' $(docker ps -q).

The daemon did not restart, or /etc/subuid//etc/subgid lack a dockremap entry, in which case the daemon fails to start with remapping. Check journalctl -u docker and the grep dockremap output above.

The check reads the command line first, and so does the daemon. A systemd drop-in silently overrides your daemon.json; systemctl cat docker | grep ExecStart.

Sources

How the script reads this

Next

Re-run the Daemon & Socket audit after applying the fix and confirm this check moves to PASS. CtrlOps runs all 8 checks over your existing SSH connection and scores the result, so the change is visible without reading another config file.

All 8 Daemon & Socket fixes
Audit your fleet

Find every one of these on every server, in one click

CtrlOps runs this audit over your existing SSH connection - no agents, no scripts to manage. $7/user/month after a 1 month free trial - no credit card required.

Windows

✓ Start instantly·✓ No credit card·✓ No sneaky autorenewals