The Docker Socket Is Root

Half the self-hosted monitoring tools in existence want the Docker socket. So do reverse proxies that discover backends by label, backup tools that enumerate volumes, and dashboards that show you what is running.

The line is always the same:

volumes:
  - /var/run/docker.sock:/var/run/docker.sock:ro

That line gives the container root on the host.

Why#

The Docker daemon runs as root, and its API is not a restricted control surface – it is a way to ask root to do things. A process that can talk to the socket can start a container with the host filesystem bind-mounted at /host and --privileged set, then write whatever it likes wherever it likes. There is no privilege escalation step to find; the API is the escalation.

The :ro does nothing useful here#

This is the part that surprises people, and it is worth being precise about.

:ro marks the mount read-only, which stops the container changing permissions on the socket file. But a Unix socket is not a file you read and write in the ordinary sense – it is an endpoint you connect to and exchange messages over. Marking the mount read-only does not make the socket stop accepting arbitrary requests.

So the flag is not wrong, exactly. It is answering a different question from the one everybody is asking it.

What actually helps#

Put a filter in front of the socket. A socket proxy sits between the container and the daemon and refuses API calls the container has no business making, usually configured as a set of allowed endpoint groups.

The common one is Tecnativa/docker-socket-proxy, which is configured by environment variable, defaults to denying everything, and is turned on one endpoint group at a time:

services:
  socket-proxy:
    image: tecnativa/docker-socket-proxy
    environment:
      CONTAINERS: 1        # list and inspect containers
      POST: 0              # no state-changing calls at all
    volumes:
      - /var/run/docker.sock:/var/run/docker.sock:ro
    networks: [socket]

  dashboard:
    image: some/dashboard
    environment:
      DOCKER_HOST: tcp://socket-proxy:2375
    networks: [socket]

POST: 0 is doing most of the work. Nearly everything dangerous in the Docker API is a POST – creating containers, starting them, executing into them – and the tools that merely want to see what is running need none of them.

The proxy still has the socket, so it is still the thing to keep small and patched. The gain is that one container has root instead of five, and that one container runs a small piece of software whose entire job is to say no.

The question to ask each container#

Not "does this need the socket" but "which four API calls does this need." Most monitoring tools need to list containers and read their stats. That is a read-only subset, and it is the difference between a compromised dashboard leaking a container list and a compromised dashboard owning the host.

Treat that question as an inventory item, not a one-time compose-file review. Whenever a container gains socket access, record the endpoints it needs, put it behind the narrowest proxy policy that works, and revisit the policy when the container is upgraded. A socket mount is infrastructure-level access; it deserves infrastructure-level change control.

Sources#