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
container1:
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
- WouterD. How to get a Docker container's IP address from the host, StackOverflow. Retrieved 2020-10-25.
- Chaifeng. To Fix The Docker and UFW Security Flaw Without Disabling Iptables, Github. Retrieved 2020-10-25.