A guide to Docker Networking

If you’re using Docker to run your application workloads, it’s possible that you’ve been using the “docker run” command for some time, and haven’t given networking any thought. Docker networking is the process of creating and managing networks that allow Docker containers to communicate both with each other and with the outside world. It provides a way for containers to connect to each other and to the host system, enabling them to share data and resources. Docket networks enable:

  • Isolation: Docker networks provide a level of isolation between containers, which can prevent interference and conflicts when multiple containers are running on the same host
  • Security: Docker networks allow you to secure network traffic between containers and restrict access to sensitive data or resources
  • Scalability: Enable communication and interaction between containers running on different hosts, which can help scale applications across multiple hosts and reduce the risk of single points of failure, ultimately resulting in an increase of operational efficiency. 
  • Ease of Management: Docker networks can be easily created, managed, and configured using the Docker CLI or Docker Compose. This can simplify the management of network resources and help ensure consistent network configurations across different environments.

 

In this blog, I’ll delve further into Docker networking, including how to build Docker networks, start containers inside of those networks, and more.

Docker Networking

There are several types of networks available in Docker, including:

  1. Bridge: This is the default network created when Docker is installed. It permits communication between containers and the host system, but not with external networks.
  2. Host: This network allows containers to access the host’s IP address and network interfaces by sharing the host’s network stack. This can provide better network performance, but may also introduce security risks.
  3. None: In addition to having no connectivity to the external network or to other containers, this mode will not configure an IP address for the container. It is capable of batch task execution and does have the loopback address.

Docker Network Commands

 

# list docker networks
docker network ls

# create a docker container
docker network create <network name>

# remove an existing network
docker network rm <network name>

# launch container in a specific network
docker run -d  –network=<network name> -p 80:80 nginx

# inspect container to check if it is launched in correct network
docker inspect <container-id> -f “{{json .NetworkSettings.Networks }}”

# disconnect container from a specific network
docker network disconnect <network_name> <container_id>

Build Two-Tier Architecture

In this example, we will create a two-tier architecture with two networks, named frontend and backend, having containers deployed.

Create a custom image

Let’s begin by creating a custom docker image to be used for all our examples. 

Create Dockerfile

FROM httpd
RUN apt-get update
RUN apt-get install -y iputils-ping
RUN apt-get install -y inetutils-traceroute
RUN apt-get install -y iproute2
RUN apt-get install -y curl telnet dnsutils vim

Build an image using this Dockerfile. This image instals all the required utilities.

docker build . -t testimage

Launch containers in the default network (bridge)

Run the containers without specifying any network.

docker run -d –name s1 -p 8080:80 testimage
docker run -d –name s2 -p 8081:80 testimage

Since both s1 and s2 are within the same network, s2 is accessible from s1. Let’s check it out.

❯ docker exec -it s1 bash
root@0645abeb7144:/usr/local/apache2# ping 172.17.0.3
PING 172.17.0.3 (172.17.0.3) 56(84) bytes of data.
64 bytes from 172.17.0.3: icmp_seq=1 ttl=64 time=3.53 ms
64 bytes from 172.17.0.3: icmp_seq=2 ttl=64 time=0.176 ms
64 bytes from 172.17.0.3: icmp_seq=3 ttl=64 time=0.096 ms
64 bytes from 172.17.0.3: icmp_seq=4 ttl=64 time=0.088 ms
^C
— 172.17.0.3 ping statistics —
4 packets transmitted, 4 received, 0% packet loss, time 3076ms
rtt min/avg/max/mdev = 0.088/0.971/3.525/1.474 ms
root@0645abeb7144:/usr/local/apache2#

Create Docker Networks

 

# create networks
docker network create frontend –subnet 10.0.0.0/24
docker network create backend –subnet 10.0.1.0/24

# check if networks are created successfully
❯ docker network ls
NETWORK ID     NAME                          DRIVER    SCOPE
288dea7b22ed   backend                       bridge    local
3dfd30173a8f   bridge                        bridge    local
60cfb835a7a8   frontend                      bridge    local
e4322daa7885   host                          host      local
9ee7282a88c8   none                          null      local

Launch containers in new networks

Let’s move both of our containers in each network.

# remove existing containers and relaunch them in separate networks
docker stop s1 s2 && docker rm s1 s2

# attach containers to newly created networks
docker run -d –name s1 -p 8080:80 –cap-add=NET_ADMIN –network frontend testimage
docker run -d –name s2 -p 8081:80 –cap-add=NET_ADMIN –network backend testimage

Let’s test the connectivity between containers.

# check container details by running
docker inspect s1
docker inspect s2

❯ docker exec -it s1 bash
root@855326a3f6fe:/usr/local/apache2# ping 10.0.1.2
PING 10.0.1.2 (10.0.1.2) 56(84) bytes of data.
^C
— 10.0.1.2 ping statistics —
3 packets transmitted, 0 received, 100% packet loss, time 2061ms

root@855326a3f6fe:/usr/local/apache2#exit
exit

❯ docker exec -it s2 bash
root@84b05c2177ac:/usr/local/apache2# ping 10.0.0.2
PING 10.0.0.2 (10.0.0.2) 56(84) bytes of data.
^C
— 10.0.0.2 ping statistics —
2 packets transmitted, 0 received, 100% packet loss, time 1056ms

root@84b05c2177ac:/usr/local/apache2# exit
exit

This is what the new architecture will look like.

Create Gateway Network to connect Frontend and Backend

Now, we need to add a gateway which can act as a bridge for both s1 and s2 to communicate with each other.

# create a gateway network container and launch in frontend network
docker run –name gw –network frontend -d testimage

# connect this container to backend network
docker network connect backend gw

This is what the architecture looks like with gw container.

Source

Destination

Reachability

Comments

gw

s1

✅ 

since gw is deployed in frontend network

s1

gw

✅ 

since gw is deployed in frontend network

gw

s2

✅ 

since gw is deployed in backend network

s2

gw

✅ 

since gw is deployed in backend network

s1

s2

There is still no route for s1 to s2

s2

s1

There is still no route for s2 to s1

 

docker exec -it gw bash
# ping s1
root@1bd191b5f26a:/usr/local/apache2# ping 10.0.0.2
PING 10.0.0.2 (10.0.0.2) 56(84) bytes of data.
64 bytes from 10.0.0.2: icmp_seq=1 ttl=64 time=2.73 ms
64 bytes from 10.0.0.2: icmp_seq=2 ttl=64 time=0.093 ms
^C
— 10.0.0.2 ping statistics —
2 packets transmitted, 2 received, 0% packet loss, time 1001ms
rtt min/avg/max/mdev = 0.093/1.413/2.733/1.320 ms

# ping s2
root@1bd191b5f26a:/usr/local/apache2# ping 10.0.1.2
PING 10.0.1.2 (10.0.1.2) 56(84) bytes of data.
64 bytes from 10.0.1.2: icmp_seq=1 ttl=64 time=0.186 ms
64 bytes from 10.0.1.2: icmp_seq=2 ttl=64 time=0.176 ms
64 bytes from 10.0.1.2: icmp_seq=3 ttl=64 time=0.144 ms
^C
— 10.0.1.2 ping statistics —
3 packets transmitted, 3 received, 0% packet loss, time 2066ms
rtt min/avg/max/mdev = 0.144/0.168/0.186/0.017 ms

#similarly gw is reachable from s1 and s2. however, s2 is still not accessible from s1

❯ docker exec -it s1 bash
root@855326a3f6fe:/usr/local/apache2# ping 10.0.1.2
PING 10.0.1.2 (10.0.1.2) 56(84) bytes of data.
^C
— 10.0.1.2 ping statistics —
3 packets transmitted, 0 received, 100% packet loss, time 2085ms

root@855326a3f6fe:/usr/local/apache2#

Allow connection from Frontend to Backend network

To allow connection from s1 to s2 containers, we need to add a route for the backend subnet in the s1 container.

❯ docker exec -it s1 bash
root@fdecbc3ed6b1:/usr/local/apache2# hostname -i
10.0.0.2
root@fdecbc3ed6b1:/usr/local/apache2# nslookup gw
Server: 127.0.0.11
Address: 127.0.0.11#53

Non-authoritative answer:
Name: gw
Address: 10.0.0.3

root@fdecbc3ed6b1:/usr/local/apache2# ip route add 10.0.1.0/24 via 10.0.0.3

root@fdecbc3ed6b1:/usr/local/apache2# traceroute 10.0.1.2
traceroute to 10.0.1.2 (10.0.1.2), 64 hops max
  1   10.0.0.3  0.042ms  0.037ms  0.042ms
  2   * ^C

Adding just one route will not make it work, as it is clearly seen in the output of traceroute. The frontend container knows how to go to the IP belonging to the backend network subnet range, but there is no response from the backend.

This is the current architecture.

The final step is to add a route in the backend container.

 

❯ docker exec -it s2 bash
root@1069e4c4e7d0:/usr/local/apache2# hostname -i
10.0.1.2
root@1069e4c4e7d0:/usr/local/apache2# nslookup gw
Server: 127.0.0.11
Address: 127.0.0.11#53

Non-authoritative answer:
Name: gw
Address: 10.0.1.3

root@1069e4c4e7d0:/usr/local/apache2# ip route add 10.0.0.0/24 via 10.0.1.3
root@1069e4c4e7d0:/usr/local/apache2# traceroute 10.0.0.2
traceroute to 10.0.0.2 (10.0.0.2), 64 hops max
  1   10.0.1.3  0.035ms  0.079ms  0.120ms
  2   10.0.0.2  0.037ms  0.039ms  0.038ms
root@1069e4c4e7d0:/usr/local/apache2#

In this blog, we built a two-tier architecture to demonstrate how containers in different networks can talk to each other and the importance of Docker networking. Though theoretically, we could construct a third network to support database containers and add a route specific to the backend network.

I hope this article was useful in understanding the fundamentals of Docker networking. In future articles, we will see how these Docker networking concepts are applied to AWS Elastic Container Service and further understand ECS networking modes.

Enjoyed this blog?

Share it with your network!

Move faster with confidence