Securing Docker Deployments: Network Isolation and Traefik Routing

Block unwanted outbound connections

Docker has become synonymous with containerization, offering unparalleled flexibility and scalability for deploying applications. However, as Docker adoption continues to soar, so do concerns surrounding container security. One of the critical challenges is ensuring that containers have controlled access to the internet to mitigate the risk of malicious activity.

Networking is fun!

In this post, we explore how Traefik, a modern reverse proxy and load balancer, can be leveraged, together with proper network isolation, to enhance the security of Docker containers by isolating their Internet access. By implementing Traefik with restricted Docker networks, not only can you fortify your containerized environment against potential threats, but you can also maintain seamless connectivity for legitimate traffic. Tips for properly securing the execution of docker containers are explained in my previous article.

Let’s quickly summarize the whole point of this blog post.

  • Goal: Block containers Internet connectivity, while still allowing inbound traffic on exposed ports.
  • Reason: Avoid unwanted outbound connections from containers, especially the ones you trust less. Prevent additional damage in case of supply-chain attacks or compromised image repositories or in-container applications.
  • Solution: Multiple docker networks. A set of internal only networks and one only exposed network available to the ingress controller (Traefik in this solution)
  • How: Below I will post examples and complete docker compose file.

Here is a Figure representing how the different networks are setup and the normal flow of an HTTP request towards two internal applications.

Representation of the different Docker networks

In such a network design, the containers within each Docker compose project will never be able to connect to the Internet. Although, when configuring Traefik, containers that need to expose a service externally can still be reached through a series of internal networks.

This prevents, for example, malicious container images to perform unwanted network activity outside of the local, private, internal only docker network.

Containers in each project will not be able to reach Internet

You can isolate more each compose stack by creating ad-hoc “net-proxy” per-stack then adding them to the Traefik container.

I came to this solution while exploring alternative, and possibly easy to maintain, ways to isolate containers and prevent them from unrestricted network access. This became especially important in self-hosted / home lab environments where Docker images are pulled from repositories and more often than not, guaranteeing the integrity of the containers is not possible.

One of the first solutions I tried to implement was a carefully designed iptables script that, using the DOCKER-USER chain, would allow selected containers to connect or not to the Internet, and to what address. I realized quickly that the approach would not work, as the firewall rules would need to be refreshed each time a container/network is rebuilt. Hence why I tried the mentioned approach with the help of Traefik, which I never had the opportunity to give it a go.

My ongoing goal is to secure the environment also against potentially malicious containers being deployed. This translates into a quite thorough list of defense in depth countermeasures, whereas network isolation is just one part. Assuming a repository introduces malicious code to a container, blocking internet connectivity is a great first step to prevent i.e. exfiltration or remote command execution.

Practical example

In the example below, I created a single Docker compose with three containers. In order to simplify, instead of creating multiple docker compose files, they are here merged into one.

The containers are the following:

  • Traefik: router for requests towards the different internal networks
  • Nginx (app1): Part of a “Compose project 1”
  • Nginx (app2): Part of a “Compose project 2”

The Compose project 1 & 2 are using separate networks, while the Traefik container is including the proxy network for each compose project, thus allowing it to route requests from the external towards the internal containers.

Before running docker compose up, you need to manually create the proxy docker networks.

This is done with docker network create --internal net-proxy (the same for net-proxy2).

The net-internal1 and net-internal2 are private networks used by each Compose stack containers to communicate between one another. For example: the web application wants to connect to a Redis or MySQL instance through this network. The net-proxy is an internal network used exclusively by the Traefik proxy and the container exposing the externally reachable service. These networks are internal, meaning they are not routed towards Internet.

The only docker network that has outbound Internet access is the net-exposed attached to the Traefik container.

Note: in the example below the Traefik container has access to the docker socket, which is to carefully take into considerations. Alternative setups involve the use of docker-socket-proxy. Lastly, for the sake of simplicity this example uses only HTTP on port 80 and it does not involve any HTTPS / TLS certificates setup.

Here you can find the docker-compose example.

Here you can see the traefik.yml configuration file.

Now you might be able to sleep a bit more peacefully at night.

I hope you found this post helpful. If you have any questions or feedback, feel free to leave a comment below.

Last modified: 14 April 2024