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 | When | What it means |
|---|---|---|
| SKIP | Docker not installed | Nothing was checked. With no docker binary present, neither the command line nor daemon.json was consulted. |
| PASS | Configured with a non-empty value | The 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. |
| WARN | Not configured | No 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 fromdocker ps/imagesuntil re-pulled. Bind-mounted host files owned by root become unreadable inside the container. Containers that need--pid=host,--net=hostor--privilegedmust 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=usernsRe-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 -dVerify 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/subgidDebugging
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
- Docker: Isolate containers with a user namespace
- Docker: dockerd
--userns-remap - subuid(5) / subgid(5)
- user_namespaces(7)
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