Wearing your docker.sock on your sleeve? Use a proxy instead
Certain containers need access to your docker socket, which allows them to view and have access to all docker containers and settings. Portainer is a great example of this.
One way of allowing this is to map the following volume to the container:
volumes:
- /var/run/docker.sock:/var/run/docker.sock:roBut doing this, especially in a publicly accessible container, is a security risk.
Instead we use a socket proxy to limit access and keep our system protected, and for that, you guessed it, we need to create another container. Luckily this works pretty easily with minimal fuss.
Prerequisites
- Docker and docker-compose installed on your machine
- sudo privileges to be able to run docker commands as root, or you've added your user to the docker group to bypass that
- The ability to SSH / use CLI/terminal on your machine, or use Portainer to spin up your stacks
Creating the container
- Create your
docker-compose.ymlfile, and then copy the below into it:
networks:
socket_proxy:
name: socket_proxy
ipam:
config:
- subnet: 172.100.0.0/24 #change subnet as necessary
services:
socket-proxy:
container_name: socket-proxy
image: tecnativa/docker-socket-proxy
restart: always
networks:
- socket_proxy
# privileged: true # true for VM. False for unprivileged LXC container.
ports:
# - "127.0.0.1:2375:2375" # Port 2375 should only ever get exposed to the internal network. When possible use this line.
# I use the next line instead, as I want portainer to manage multiple docker endpoints within my home network.
- "2375:2375"
volumes:
- "/var/run/docker.sock:/var/run/docker.sock:ro"
environment:
- LOG_LEVEL=info # debug,info,notice,warning,err,crit,alert,emerg
## Variables match the URL prefix (i.e. AUTH blocks access to /auth/* parts of the API, etc.).
# 0 to revoke access.
# 1 to grant access.
## Granted by Default
- EVENTS=1
- PING=1
- VERSION=1
## Revoked by Default
# Security critical
- AUTH=0
- SECRETS=0
- POST=1 # Watchtower
# Not always needed
- BUILD=0
- COMMIT=0
- CONFIGS=0
- CONTAINERS=1 # Traefik, portainer, etc.
- DISTRIBUTION=0
- EXEC=0
- IMAGES=1 # Portainer
- INFO=1 # Portainer
- NETWORKS=1 # Portainer
- NODES=0
- PLUGINS=0
- SERVICES=1 # Portainer
- SESSION=0
- SWARM=0
- SYSTEM=0
- TASKS=1 # Portainer
- VOLUMES=1 # PortainerWhat are we doing here?
- We're creating a docker bridge network called
socket_proxy - We're creating a container called
socket-proxyand connecting it to our docker network - We're making this container accessible at port
2375 - I want it to always restart whenever docker or the system restarts, as other containers depend on it
- We are giving it access to and specifying which parts of the
docker.sockit can read (0= no access,1= access)
- Spin up the container using Portainer or your terminal (
docker-compose up -d)
Connecting a container to the socket proxy
Now that the container is up and running, we can use it in place of the volume mapping mentioned above for those containers needing docker socket access.
To do this, we need to add one env variable, and connect the container in question to the socket_proxy docker network. Let's take Watchtower as an example, and the following docker-compose.yml for it:
networks:
default:
name: socket_proxy
external: true
services:
watchtower: #automatic container version monitoring and updating
container_name: watchtower
image: containrrr/watchtower:latest-dev
environment:
- TZ=$TZ
- DEBUG=true
- WATCHTOWER_LABEL_ENABLE=true
- WATCHTOWER_CLEANUP=true
- WATCHTOWER_INCLUDE_RESTARTING=true
- WATCHTOWER_INCLUDE_STOPPED=true
- WATCHTOWER_NOTIFICATIONS=shoutrrr
- WATCHTOWER_NOTIFICATION_URL=pushover://shoutrrr:$PUSHAPI@$PUSHKEY/?devices=$PUSHDEVICE
- DOCKER_HOST=tcp://socket-proxy:2375
command: --interval 21600
restart: unless-stopped
volumes:
- $LOCALTIME
networks:
- socket_proxy- Note the
DOCKER_HOSTin theenvironmentblock, and thenetworks - This assumes the socket-proxy container is in a different docker-compose file to Watchtower. If they were in the same compose file, you could simply write
DOCKER_HOST=[servicename], so in our case,DOCKER_HOST=socket-proxy
You now have a functioning proxy to protect your docker.sock and you can make the relevant changes to your associated compose files as necessary.
Not all containers are the same
Not all containers are created equal. This means that while the above should work for most of your containers, not all will necessarily be able to read the - DOCKER_HOST line. Some may require a command, so read the documenation for them or on the socket proxy github repo.
Portainer
Something like Portainer for instance requires a bit of a change to the compose file.
Just above the environment variable, add the following:
command: -H tcp://socket-proxy:2375Then go into the gui and make a change to the environment endpoint:

- Either modify your existing environment or add a new one and delete the old one. In any case, your final page should look something like this:

Namecan be anything- The
URLis your socket-proxy container name and the container IP - Your public IP is your host machine's IP
- Don't forget to ensure your Portainer container is on the same docker network as your socket proxy container
Hopefully the above has given you a good start on protecting your docker socket from prying eyes. It's not a catch-all, but for those containers with which it doesn't work directly out the box you can normally find documentation to help you out.
Related Articles


