How To Secure Your Docker Environment By Using a Docker Socket Proxy
In my previous article, I talked about Docker security tips that are often overlooked while working with Docker. One critical flaw was mounting the Docker Socket directly into containers which can be a major vulnerability. I recommended using a Docker Socket Proxy that is a simple but powerful solution.
In this article, we will dive deeper into this topic. We will explore the Docker Socket Proxy and learn why exposing it directly into a container is a dangerous practice. We will use it to harden our container infrastructure. Additionally, we will make use of the newly deployed Docker Socket Proxy in a containerized application (Portainer) to communicate with the Docker API in a safe way.
Let’s make our containers smarter and safer.
What Is the Docker Socket?
In any Docker environment, the Docker Socket (/var/run/docker.sock
) refers to a special file within the Linux OS that allows direct communication with the Docker daemon. The Docker daemon is the core process that manages every container on the host system. This means that every application (or container) that has access to the Docker Socket can fully manage containers (e.g., create, stop, delete, or modify), which results in full control over the entire Docker environment.
Why Is Exposing the Docker Socket Risky?
If the Docker Socket is mounted inside a Docker container (which many containers like Portainer want for convenience), we are giving that container full control over the Docker Daemon.
Now, if an attacker can gain access to our container, they can:
- Delete and modify every container in our Docker environment
- Create new compromised Docker containers with run harmful software
- Escape from the container to take over the host
/
volume. Now, just change the root password by manipulating /etc/passwd
. Then, apply your connection information to .ssh
(public/private key) to connect with SSH to the host.This also works if the Docker socket is mounted as read-only because attackers can often find ways to execute commands through it.
What Is a Docker Socket Proxy?
In contrast to the simple Docker Socket, the Docket Socket Proxy acts as a middleware between our containers and the Docker Socket. This results in not exposing the Docker Socket directly to all our containers.
With the Docket Socket Proxy, we can:
- Limit all actions that the container can do by granting only permissions that are necessary
- Allow only specific containers to use the Proxy instead of exposing the full socket to everyone
- Run a small, well-defined proxy container that is controlled and can be used to access the Docker API
Benefits of Using a Docker Socket Proxy
Using a Docker Socket Proxy in our Docker environment will introduce the following benefits:
- It minimizes the risk by restricting which actions can be done by applications.
- It prevents direct access to the Docker Daemon, which reduces attack surfaces in our environment
- It allows controlled and secure automation while still maintaining security.
How to Deploy A Docker Socket Proxy
To set up our own Docker Socket Proxy, we will use the LinuxServer.io version of the Docket Socket Proxy to restrict the API access.
To set it up, we first need to create a Compose file called docker-compose.socket-proxy.yaml
:
version: '3.8'
services:
socket-proxy:
image: lscr.io/linuxserver/docker-socket-proxy:latest
container_name: docker_proxy
environment:
- LOG_LEVEL=info # Set logging level
- CONTAINERS=1 # Allow listing and managing containers
- SERVICES=1 # Allow listing and managing services
- TASKS=1 # Allow listing tasks in the swarm
- NODES=1 # Allow listing nodes in the swarm
- NETWORKS=1 # Allow listing networks
- VOLUMES=1 # Allow listing volumes
- EXEC=0 # Disable exec command for security
- SYSTEM=0 # Block system-level API access
volumes:
- /var/run/docker.sock:/var/run/docker.sock:ro
networks:
- proxy-net
deploy:
mode: global
restart_policy:
condition: on-failure
networks:
proxy-net:
external: true
In this Compose file, we used several environment variables that permit or allow the Docker Socket Proxy to do certain tasks. If we want to allow/permit other functionalities, we can find the setting in this list and add it to our environment variables.
Before starting the Docker Socket Proxy, we have to create an external network because it was specified as one in the Compose file. We can do this with the following command:
docker network create proxy-net
-d overlay
to the network creation commandNow that we have the network created, we can start the Compose file with the Docker Socket Proxy by executing:
docker compose -f docker-compose.socket-proxy.yaml up -d
Deploy Portainer with the Docker Socket Proxy
Assuming that the Docker Socket Proxy is running we can start to configure and deploy Portainer with the Docker Socket Proxy instead of directly accessing the Docker Socket.
To do this, we have to create a Compose file named docker-compose.portainer.yaml
with the following contents:
version: '3.8'
services:
portainer:
image: portainer/portainer-ce:latest
command: -H tcp://socket-proxy:2375 --tlsskipverify
ports:
- "9000:9000" # Portainer Web UI
- "9443:9443" # Secure Portainer Web UI
volumes:
- portainer_data:/data
networks:
- proxy-net
deploy:
mode: replicated
replicas: 1
restart_policy:
condition: on-failure
networks:
proxy-net:
external: true
volumes:
portainer_data:
In this Compose file, there are two very important settings that differ from a normal Portainer deployment with direct access to the Docker Socket:
1. The command uses a TCP connection to the Socket proxy container: tcp://socket-proxy:2375
socket-proxy
!2. The network that is used is the previously created external network proxy-net
To start the Portainer Docker container, we can execute:
docker compose -f docker-compose.portainer.yaml up -d
After some seconds, the command should be finished,d and Portainer is started. To access Portainer, we can switch to our browser and open:
http://localhost:9000
(for HTTP)https://localhost:9443
(for HTTPS)
Closing Notes
In this very short tutorial, we learned how to set up the Docker Socket Proxy container, use it within a Portainer instance to securely access the Docker API, and allow minimal exposure. This should add an extra layer of trust and security.
But, why is this secure?
- ✅ It prevents us from full Docker socket exposure. This restricts API access, so Portainer (or other containers) only gets the permissions it needs.
- ✅ It reduces attack surface because no direct socket access is possible and sensitive API actions like
EXEC
andSYSTEM
are blocked. - ✅ It works in Swarm mode because it uses an overlay network for secure communication between containers
So, in Conclusion, we can say that using a Docker Socket Proxy is a much safer alternative to the normal approach where we expose /var/run/docker.sock
directly. Additionally, it is very easy to do, and we should take the extra step to secure our environment.
Let's all make our environments more secure 😊🚀!
I hope you find this tutorial helpful and can and will use the Docker Socket Proxy in your Docker environment. Also, I would love to hear your ideas and thoughts about the Docker Socket Proxy. If you can provide more information or have other important settings to set, don’t hesitate to comment here. If you have any questions, please write them down below. I try to answer them if possible.
Feel free to connect with me on Medium, LinkedIn, Twitter, BlueSky, and GitHub.