Mounting docker.sock into a container is root on the host
It isn't a bug and it isn't a misconfiguration you can patch. It's the Docker API working exactly as designed - which is why it's worth understanding before you decide what to do about it.
Serhii Drobot · · 6 min read
What the mount actually does
You've seen the line. It's in half the docker-compose.yml files on GitHub:
services:
some-tool:
volumes:
- /var/run/docker.sock:/var/run/docker.sock
/var/run/docker.sock is a Unix socket. It's the Docker daemon's API endpoint - the same API the docker CLI talks to. The daemon runs as root. It has no user model, no permissions, no roles: anything that can write to that socket can tell the daemon to do anything the daemon can do.
Mounting it into a container hands that container the full API. And the Docker API can, by design, start a new container that mounts the host's filesystem and runs privileged. So the container you gave the socket to can read /etc/shadow, write to /root/.ssh/authorized_keys, or install anything it likes - on the host, as root.
That's the whole thing. There's no exploit, no CVE, no version to patch. Docker's own documentation says it plainly. The socket is root access, delivered over a file.
Why this is worse than it sounds
The usual reaction is "sure, but that container is ours, we trust it." The problem is that the container isn't the threat model. The threat model is everything that ends up executing inside that container:
- A remote-code-execution bug in the app running there - now a web bug is host root, not a container problem
- A compromised npm or PyPI package pulled in at build time
- A base image that changed upstream since you last looked
- Anyone who can trigger a build or exec into the container in CI
Container isolation is the thing that normally turns "attacker owns your app" into "attacker owns one container." The socket mount removes that step. It converts every bug in that container into a full host compromise, and on most single-host setups, a full host compromise is every customer's data.
It's also invisible in the places people look. docker ps won't warn you. Nothing in your monitoring will fire. The container is behaving exactly as configured.
Check whether you have it
The quick version, if you know where to look:
docker ps -q | xargs docker inspect \
--format '{{ .Name }}: {{ range .Mounts }}{{ .Source }} {{ end }}' \
| grep docker.sock
The thorough version, which also catches privileged containers, capability leaks, missing resource limits and the rest of the host's exposure in the same pass:
sudo ./owlzops-mapper audit
Sensitive mounts show up flagged as DOCKER_SOCKET. It's a single static binary, read-only, nothing leaves the server, and it's free - source and install here. Read the code before you run it; that's rather the point.
Why you probably can't just delete the line
This is where most write-ups stop, right after "never mount the Docker socket." Useful advice, unless you're the person whose reverse proxy stops routing the moment you follow it.
The socket is usually there because something legitimately needs the Docker API:
- Traefik reads container labels to build routes. No socket, no routing.
- Portainer is a Docker management UI. The socket is the entire product.
- Watchtower pulls and restarts containers. Same.
- CI runners doing docker-in-docker builds.
- cAdvisor, Netdata, logging agents reading container metadata.
Pull the mount out of any of those and the thing stops working - usually in a way you notice in production, not in review. So "remove it" isn't a plan. The real question is narrower and more useful: does this container need the whole API, or three read-only endpoints?
Almost always, it's three read-only endpoints.
What to do instead
1. Remove it where it's cargo cult
Some of those mounts are there because someone copied a compose file in 2021 and nothing ever needed the socket. Check each one. Free wins first.
2. Put a socket proxy in front of it
For everything that genuinely needs the API: don't hand it the socket, hand it a filtered view. A socket proxy sits between the container and the daemon and only allows the endpoints you list. Traefik needs to read containers and events. It never needs POST /containers/create. So don't allow it.
services:
socket-proxy:
image: tecnativa/docker-socket-proxy
volumes:
- /var/run/docker.sock:/var/run/docker.sock:ro
environment:
CONTAINERS: 1 # allow read of container list
EVENTS: 1 # allow the event stream
POST: 0 # deny every write endpoint
networks: [ proxy-net ]
traefik:
# no docker.sock mount at all
command:
- --providers.docker.endpoint=tcp://socket-proxy:2375
networks: [ proxy-net ]
Now a compromise of Traefik gets an attacker a list of your containers. Not root. That's the entire difference, and it's about twenty minutes of work.
Note the :ro on the socket mount. It matters less than it looks - it makes the socket file read-only, not the API - but the proxy is what actually enforces the boundary. Don't mistake :ro alone for a fix.
3. If you can't do either, watch it
Sometimes the tool won't work through a proxy and you're stuck. That's a real situation, not a failure. When you can't remove a risk, you make it loud: log every container the daemon creates, alert on any container starting privileged or mounting the host root, and make sure someone actually reads the alert. That's a compensating control - worse than removing the path, much better than pretending it isn't there.
The short version
- The Docker socket is root. Not "similar to root" - root.
- Mounting it into a container makes every bug in that container a host compromise.
- You often can't just remove it, because real tools legitimately need the API.
- A socket proxy gives them what they need and nothing else. That's the fix, and it's cheap.
- Where you're stuck, log it loudly instead of hoping.
Not sure what else is open?
A socket mount is rarely the only thing. If it's there, there's usually a privileged container, an SSH config nobody revisited, and a firewall that was never switched on. An Infrastructure Security Audit is three days, read-only, $2,995 - every exposure ranked by what an attacker reaches first, with the remediation order. Run the free scanner first - if it comes back clean, we'll tell you so rather than sell you an audit.
Or just run the scanner and read the output. It costs nothing and we're not in the room.