Joe's
Digital Garden

Docker Networking

Note detailing issues with and solutions for different Docker networking issues that I have encountered. Unless mentioned otherwise, this note assumes operating Docker using an Ubuntu 20.04 host.

Accessing Services via the Local and Internal Networks

When we define a set of services using docker-compose they will, by default, create their own internal network. We can access this network from the host via the services IP addresses or exposed ports and the services inside the container can access each other via the names of their services.

Let's start with an example docker-compose.yml for a simple PHP application that connects to a MySQL container:

version: '3'

services:
    php-application:
        hostname: app.docker
        ports:
            - 8081:80
        depends_on:
            - php-mysql
        ...
    php-mysql:
        ports:
            - 3309:3306
        ...

This docker-compose configuration defines two serves "php-application" and "php-mysql." They both use the default ports internally for their respective services (80 and 3306 respectively) and map to ports 8081 and 3309 respectively on the host machine.

Routing on the Host to app.docker

The docker-compose configuration defines the php-application service to have a hsotname of "app.docker." If we start an interactive shell inside this container and run hostname the result will output app.docker.

First we need to get the internal IP address of the php-application container (1):

docker inspect \
    -f '{{range.NetworkSettings.Networks}}{{.IPAddress}}{{end}}' \
    container_name_or_id

We then edit the host /etc/hosts file and add the following to the end:

{docker_ip_address} {service_hostname}.localdomain {service_hostname}

We should now be able to access the Apache server running inside the container from our host machine using the service hostname as our domain name:

curl http://app.docker

Accessing the MySQL Server

Routing between the php-application service and the php-mysql service is simple. The host name for the mysql service is simply it's name: php-mysql. So if we were in an interactive shell inside the php-application service, reaching the mysql database is simple.

From the Container:

mysql -u root -p$PASSWORD -h php-mysql -P 3306

If we need to access the MySQL server from the host machine we can run docker ps to find the IP and ports that it has bound to, but typically for this docker-compose configuration it will be 0.0.0.0:3309.

From the Host:

mysql -u root -p$PASSWORD -h 0.0.0.0 -P 3309

Docker and the UFW Firewall

Getting Docker to play nice with UFW is fiddly.

By default, it appears that Docker bypasses the UFW rules. So if the container binds to port 80 on the host, then that container will be publically exposed on port 80 even if UFW says it's denying access to port 80!

Setting Up UFW

Feng gives an answer on StackOverflow that has worked for me to achieve the behavior that I expect.

First, add the following to /etc/ufw/after.rules:

# BEGIN UFW AND DOCKER
*filter
:ufw-user-forward - [0:0]
:DOCKER-USER - [0:0]
-A DOCKER-USER -j RETURN -s 10.0.0.0/8
-A DOCKER-USER -j RETURN -s 172.16.0.0/12
-A DOCKER-USER -j RETURN -s 192.168.0.0/16

-A DOCKER-USER -j ufw-user-forward

-A DOCKER-USER -j DROP -p tcp -m tcp --tcp-flags FIN,SYN,RST,ACK SYN -d 192.168.0.0/16
-A DOCKER-USER -j DROP -p tcp -m tcp --tcp-flags FIN,SYN,RST,ACK SYN -d 10.0.0.0/8
-A DOCKER-USER -j DROP -p tcp -m tcp --tcp-flags FIN,SYN,RST,ACK SYN -d 172.16.0.0/12
-A DOCKER-USER -j DROP -p udp -m udp --dport 0:32767 -d 192.168.0.0/16
-A DOCKER-USER -j DROP -p udp -m udp --dport 0:32767 -d 10.0.0.0/8
-A DOCKER-USER -j DROP -p udp -m udp --dport 0:32767 -d 172.16.0.0/12

-A DOCKER-USER -j RETURN
COMMIT
# END UFW AND DOCKER

and then restart the ufw service with systemctl.

Allowing Public Access to a Container

At this point, the containers can be accessed by systems on any of the local networks, but the containers cannot be accessed from the public network. To enable WAN access to a container we simply run:

ufw route allow proto tcp from any to any port 80

Where port 80 is the internal port of the container.

If we are running multiple containers that are running services on port 80, we can specify the particular container to route port 80 traffic to by it's internal IP address:

ufw route allow proto tcp from any to 172.17.0.2 port 80

If the host port is different than the internal port, e.g. -p 8080:80 then we will also need to allow access to port 8080:

ufw allow proto tcp from any to any port 8081

We can now access the service from the WAN on port 8081. If example.com points at the host machine than, http://example.com:8081 will resolve to port 80 in the container.

External References

  1. WouterD. How to get a Docker container's IP address from the host, StackOverflow. Retrieved 2020-10-25.
  2. Chaifeng. To Fix The Docker and UFW Security Flaw Without Disabling Iptables, Github. Retrieved 2020-10-25.

Linked References