What this check reads
It queries the group database with getent group docker and takes field 4 of that line with cut -d: -f4, the comma-separated list of secondary members, turning the commas into spaces with tr ',' ' '. The test is then simply whether anything is left once the spaces are stripped out: an empty field is the PASS, and any name at all is the WARN, which prints every member it found. There is no allow-list and no notion of an expected account, so a CI runner or a deploy user still produces the warning.
When it applies
Always runs, needs no root, and does not need Docker to be installed or the daemon reachable - it simply reads the docker line from the group database via getent, so it also works with LDAP/SSSD-backed groups. It lists secondary members only: a user whose primary group is docker (their GID in /etc/passwd) is not in that field and is not reported. It also cannot see other routes to the socket, such as a sudo rule permitting docker, or filesystem ACLs on /var/run/docker.sock.
What each result means
| Result | When | What it means |
|---|---|---|
| PASS | No members | The docker group has no secondary members, so socket access stays with root. A user whose primary group is docker would not appear in this field. |
| WARN | Members listed (confirm each) | Every name printed is root-equivalent on this host and can take the machine over without ever appearing in a sudo log. Confirm each one is intended. |
Why it matters
Docker's post-install docs state it plainly: the docker group grants root-equivalent privileges. A member can run docker run -v /:/host alpine chroot /host without sudo and without any audit trail in sudo logs. CIS Docker 1.1.2 (v1.6: 1.2.2) "Ensure only trusted users are allowed to control Docker daemon"; OWASP Docker Cheat Sheet Rule #11 (rootless mode) as the alternative.
Why it fails, and when it is wrong
- Users whose primary group is
dockerare not listed by field 4. Checkgetent passwd | awk -F: '$4==<docker gid>'. - Membership only matters while the socket is group-owned by
docker(defaultsrw-rw---- root docker). Rootless installs have no such requirement. - CI runners and deploy users are usually in this group by necessity; treat them as administrators (key-only SSH, restricted sudo is irrelevant since they already have root via Docker).
How to fix it
List the members
The audit lists secondary members only, so check primary-group members as well.
getent group docker
awk -F: '$4=="'"$(getent group docker | cut -d: -f3)"'" {print $1}' /etc/passwdRemove the accounts that do not need it
Group membership is read at login, so end the user's existing sessions after removing them.
sudo gpasswd -d <user> docker
sudo loginctl terminate-user <user>Give the rest a narrower route
Every account left in the group is root-equivalent, so treat it as an administrator. People who run containers for themselves are better served by rootless Docker, which gives them their own daemon with no host root. Automation can use a Docker context over SSH or a socket proxy that exposes only the endpoints it needs.
Verify the fix
getent group docker
# expected: empty member list, or only accounts you can justify
# Catch the two blind spots - primary-group members and ACLs on the socket:
awk -F: '$4=="'"$(getent group docker | cut -d: -f3)"'" {print $1}' /etc/passwd
getfacl /var/run/docker.sock 2>/dev/null
sudo grep -rn docker /etc/sudoers /etc/sudoers.d 2>/dev/null
# Confirm a removed user really lost access (they must log out and back in first):
sudo -u <user> docker ps # expected: permission denied on the socketDebugging
The check has no allow-list, so any membership is reported. Each name is genuinely root-equivalent: a member can run docker run -v /:/host --privileged and own the machine, entirely bypassing sudo logging. Confirm each is intended and record the exception.
Check the blind spots above: primary-group membership, a sudo docker rule, or an ACL on the socket. Any of those grants the same power without appearing in the group's member list.
Group membership is evaluated at login. They keep it until every session ends; verify with id <user> (the database) versus sudo -u <user> id and have them log out fully.
You largely cannot: socket access is root-equivalent by design. The real options are rootless Docker (see the Rootless Mode check), a socket proxy exposing only specific endpoints, or an audited sudo rule for narrowly-defined commands.
getent returns nothing and the check passes. That is correct on a host where only root uses Docker.
Sources
- Docker: Manage Docker as a non-root user (warning)
- Docker: Docker daemon attack surface
- Docker: Rootless mode
- getent(1)
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