Docker Compose check · critical

Mounting the Docker socket in Docker Compose

Mounting /var/run/docker.sock gives the container full use of the Docker API. Anyone who controls that container can start a new privileged container with the host's root filesystem mounted, which is a takeover of the host. A read-only mount does not change this. The explainer marks it critical, and also flags mounts of /, /var, /var/run or /run, which contain the socket.

$ explain docker-compose.yml
! proxy mounts the Docker socket critical
line 9
fix: see below
$ 

What the free Docker Compose explainer reports on the risky example below.

Why it matters

The Docker socket is the API of the Docker daemon, and the daemon runs as root. Docker's own documentation warns that only trusted users should be able to control it, because access to the daemon amounts to root on the host.

Adding `:ro` only stops the container from changing the socket file. It can still connect and send API requests, so it can still create, start and exec into containers.

The mount is common in reverse proxies, CI runners, dashboards and backup tools that watch other containers. If one of those is exposed to the network and has a bug, the bug is a path to the host.

Risky and safer docker-compose.yml

Riskydocker-compose.yml
services:
  proxy:
    image: traefik:v3.1.6
    command:
      - --providers.docker=true
    ports:
      - "80:80"
    volumes:
      - /var/run/docker.sock:/var/run/docker.sock:ro
    restart: unless-stopped
    mem_limit: 512m
Saferdocker-compose.yml
services:
  proxy:
    image: traefik:v3.1.6
    # Routes come from files in the repository instead of the Docker API
    command:
      - --providers.file.directory=/etc/traefik/dynamic
    ports:
      - "80:80"
    volumes:
      - ./traefik/dynamic:/etc/traefik/dynamic:ro
    restart: unless-stopped
    mem_limit: 512m

How to fix it

  • Check whether the service has a mode that does not need the Docker API, such as file-based configuration, and use it.
  • If it must talk to Docker, put a socket proxy in front of the daemon that allows only the API calls it uses (often read-only listing of containers), and point the service at the proxy with DOCKER_HOST.
  • Never mount /, /var, /var/run or /run as a shortcut: they contain the socket too, and the explainer flags them for that reason.

When it's acceptable

  • Tools whose whole job is to manage containers on that host (a CI runner on a dedicated build machine, a local development dashboard) and that you trust to have root on it.
  • The socket proxy service itself, which has to mount the socket; the explainer will still flag it, and that line is the one to review carefully.

Check your own docker-compose.yml

The risky example is loaded below. Paste your own file to check it for this and every other Docker Compose rule. It runs in your browser; nothing is uploaded.

docker-compose.yml

Parsed in your browser. Nothing is uploaded.

Explanation

1 service0 networks0 volumes1 critical

Top level

proxytraefik:v3.1.6

  • Docs for image (opens in a new tab)

    Runs the image traefik:v3.1.6.

  • Docs for command (opens in a new tab)

    Runs --providers.docker=true instead of the image's default command.

  • Docs for ports (opens in a new tab)

    Publishes: host port 80 forwards to container port 80, on all network interfaces.

  • Docs for volumes (opens in a new tab)

    Mounts: bind-mounts host path /var/run/docker.sock at /var/run/docker.sock, read-only.

    Anyone who controls this container can start new containers and take over the host, even if the mount is read-only.

    Safer: Drop the socket mount, or put a restricted socket proxy in front of the Docker API.

  • Docs for restart (opens in a new tab)

    Restarts if it stops or after a daemon restart (unless-stopped), but stays down if you stopped it manually.

  • Docs for mem_limit (opens in a new tab)

    Limits memory to 512m. The container is killed if it exceeds this.

Questions

Is mounting docker.sock read-only safe?

No. The :ro flag only stops writes to the socket file. The container can still connect to it and send any Docker API request, including starting a privileged container.

Why is access to the Docker socket the same as root?

The Docker daemon runs as root and will start any container it is asked to, including one that mounts the host's root filesystem or runs privileged. Whoever can talk to the socket can ask for that.

How do I let a container see other containers without docker.sock?

Use a socket proxy that exposes only the read-only endpoints the service needs, or switch the service to a configuration mode that does not use the Docker API at all.