What this check reads
It asks the daemon directly: docker info --format '{{.SecurityOptions}}' returns the engine's security options, and the check greps that string for rootless. Present is the PASS and absent is the WARN, with no second source and no config file involved, so the answer describes only the daemon the audit managed to connect to. A daemon that could not be reached at all produces the SKIP rather than a WARN.
When it applies
Needs a reachable daemon (root, sudo, or docker group); otherwise SKIP. It asks docker info for its security options and looks for the string rootless. It therefore describes the daemon the audit connected to: on a host running a rootful daemon plus a per-user rootless one, the result depends entirely on which socket the audit reached. WARN is the expected outcome on a conventional installation - it is an advisory nudge, not a defect, which is why the severity is LOW.
What each result means
| Result | When | What it means |
|---|---|---|
| SKIP | Daemon unreachable | Nothing was determined. No daemon answered, so the check does not know whether one runs rootless or not. |
| PASS | Rootless | The daemon reports rootless among its security options, so dockerd and its containers run as an unprivileged user and an escape lands there rather than on host root. |
| WARN | Daemon runs as root (standard) | The daemon the audit reached is the conventional rootful one. That is expected on most hosts, and this is a prompt to decide rather than a defect. |
Why it matters
Rootless mode runs both dockerd and containers inside a user namespace as an unprivileged user; an escape lands as that user, not root. It is stronger than userns-remap (where the daemon itself is still root). OWASP Docker Cheat Sheet Rule #11 "Run Docker in rootless mode"; Docker docs "Run the Docker daemon as a non-root user". Podman is the daemonless equivalent.
Why it fails, and when it is wrong
- Advisory (LOW): the vast majority of hosts run rootful Docker. Rootless has limitations: no privileged ports below 1024 without
setcap/sysctl, slower networking (slirp4netns/RootlessKit), some storage drivers and--net=hostbehaviours differ, AppArmor/seccomp still apply, cgroup v2 required for resource limits. - If the audit runs as root against a per-user rootless daemon,
docker infotalks to the rootful socket (or none). SetDOCKER_HOST=unix:///run/user/<uid>/docker.sockfor the audit user.
How to fix it
Install the prerequisites
On Debian and Ubuntu:
sudo apt-get install -y uidmap dbus-user-session docker-ce-rootless-extrasOn RHEL and its rebuilds, sudo dnf install -y shadow-utils docker-ce-rootless-extras. The user needs at least 65,536 subordinate UIDs and GIDs:
grep "^$USER:" /etc/subuid /etc/subgidStop the rootful daemon if you are replacing it
If the rootless daemon replaces the system-wide one rather than running beside it:
sudo systemctl disable --now docker.service docker.socketRun the setup tool as the target user
Log in as the account that will own the daemon directly over SSH, not through sudo or su, so it has a systemd user session.
dockerd-rootless-setuptool.sh install
systemctl --user enable --now docker
sudo loginctl enable-linger "$USER"Point the CLI at the rootless daemon
docker context use rootless
# or: export DOCKER_HOST=unix://$XDG_RUNTIME_DIR/docker.sock
docker info --format '{{.SecurityOptions}}'Run the audit as this user too, or it will keep reaching the rootful daemon, or none.
Verify the fix
docker info --format '{{.SecurityOptions}}'
# expected for rootless: [... name=rootless ...]
# Which daemon are you actually talking to?
docker context ls
echo "$DOCKER_HOST"
ps -o user=,args= -C dockerd | head
# On a rootless install, the daemon runs as your user, not root:
systemctl --user status dockerDebugging
The audit account could not talk to any daemon. Check systemctl is-active docker, then docker ps as the audit user and sudo docker ps; if only the second works, run the audit as root or give the account sudo. A rootless daemon is a special case: it listens on $XDG_RUNTIME_DIR/docker.sock, so the audit only reaches it when run as the user that owns it, with DOCKER_HOST=unix://$XDG_RUNTIME_DIR/docker.sock set or the rootless Docker context selected. The Docker Inventory check reports the same SKIP and walks through it in more detail.
Expected and usually acceptable. Rootless mode is a significant architectural change, not a config flag. Treat this as a prompt to decide, and record the decision.
The audit connected to a different daemon, most likely the rootful one. Check docker context ls and DOCKER_HOST, and re-run the audit as the user that owns the rootless daemon.
Rootless Docker cannot bind ports below 1024 by default, does not support all storage drivers or --net=host, and has slower networking. Check those constraints against your workloads before migrating; Podman is the other route to the same goal.
Different control. Rootless means the daemon is unprivileged; a container can still run as UID 0 within its user namespace. See the Container User check.
Sources
- Docker: Run the Docker daemon as a non-root user (Rootless mode)
- Docker: Rootless mode known limitations
- OWASP Docker Security Cheat Sheet (Rule #11)
- RootlessKit
- Podman rootless tutorial
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