Mastering the Docker networking

Using Docker in development the right way

Few months ago I demonstrated through a practical example the reasons to understand and take advantage of Docker volumes.

In this one, I’ll try to do the same in regarding the Docker networking.

If you want to master the Docker Networking, this post is for you.

Containers live in a network

Because of the isolation nature of containers, they do not share the host network, however Docker provides network for them.

When the Docker Runtime starts, it creates 3 default networks:

$

docker network

ls

NETWORK ID NAME DRIVER SCOPE 5c65c2b3031b bridge bridge

local

cf446ef29441 host host

local

50fd86384bb9 none null

local

Enter fullscreen mode

Exit fullscreen mode

Let’s understand each one of them.

The bridge network

First, we can check the configuration by inspecting the network:

$

docker network inspect bridge

[

{

"Name"

:

"bridge"

,

"Id"

:

"5c65c2b3031b6d10f357f74f6cb5bf04af13819fca28b5458e00bb6b1d1718ec"

,

"Created"

:

"2022-06-27T23:49:43.227773167Z"

,

"Scope"

:

"local"

,

"Driver"

:

"bridge"

,

"EnableIPv6"

:

false

,

"IPAM"

:

{

"Driver"

:

"default"

,

"Options"

: null,

"Config"

:

[

{

"Subnet"

:

"172.17.0.0/16"

,

"Gateway"

:

"172.17.0.1"

}

]

}

,

"Internal"

:

false

,

"Attachable"

:

false

,

"Ingress"

:

false

,

"ConfigFrom"

:

{

"Network"

:

""

}

,

"ConfigOnly"

:

false

,

"Containers"

:

{}

,

"Options"

:

{

"com.docker.network.bridge.default_bridge"

:

"true"

,

"com.docker.network.bridge.enable_icc"

:

"true"

,

"com.docker.network.bridge.enable_ip_masquerade"

:

"true"

,

"com.docker.network.bridge.host_binding_ipv4"

:

"0.0.0.0"

,

"com.docker.network.bridge.name"

:

"docker0"

,

"com.docker.network.driver.mtu"

:

"1500"

}

,

"Labels"

:

{}

}

]

Enter fullscreen mode

Exit fullscreen mode

We can see that the network, responding through the Gateway 172.17.0.1, has no added containers at this moment.

Bridge networks provide bridge drivers so containers created in this network receive an IP address.

To confirm that, we create an NGINX container:

$

docker run

--name

nginx

--network

bridge

-d

nginx

Enter fullscreen mode

Exit fullscreen mode

Great. It’s just an NGINX web app running on the container port 80 and serving the traditional HTML page “Welcome to NGINX”.

Was the container added to the network?

$

docker network inspect bridge ...

"Containers"

:

{

"bb283ee626dbc631281fc0c27a1f02f075ab1908800965008a315cedd7f9d438"

:

{

"Name"

:

"nginx"

,

"EndpointID"

:

"f12f67c1d7488f708027c2e948b204ce09743721095d4514c9c24bedf8167191"

,

"MacAddress"

:

"02:42:ac:11:00:02"

,

"IPv4Address"

:

"172.17.0.2/16"

,

"IPv6Address"

:

""

}

}

,

Enter fullscreen mode

Exit fullscreen mode

Yes. We can see that the recently created container was added to the bridge network and has got the IP Address 172.17.0.2.

How can we communicate to a such container using its IP, in other words, can we hit http://172.17.0.2 and see the “Welcome to NGINX” HTML result?

$

wget http://172.17.0.2

-O

- Connecting to 172.17.0.2:80...

Enter fullscreen mode

Exit fullscreen mode

Nope 🙁

Containers do not share the host network

Again, containers do not share the “host” network. Which means only other containers in the same network (bridge) can talk to each other.

Okay, can we run another container and hit the NGINX then?

$

docker run

\

--network

bridge

\

alpine

\

sh

-c

"wget http://172.17.0.2 -O -"

Connecting to 172.17.0.2

(

172.17.0.2:80

)

writing to stdout <

!

DOCTYPE html> <html> <

head

>

<title>Welcome to nginx!</title> <style> html

{

color-scheme: light dark

;

}

body

{

width: 35em

;

margin: 0 auto

;

font-family: Tahoma, Verdana, Arial, sans-serif

;

}

</style> </head> <body> <h1>Welcome to nginx!</h1> <p>If you see this page, the nginx web server is successfully installed and working. Further configuration is required.</p> <p>For online documentation and support please refer to <a

href

=

"http://nginx.org/"

>

nginx.org</a>.<br/> Commercial support is available at <a

href

=

"http://nginx.com/"

>

nginx.com</a>.</p> <p><em>Thank you

for

using nginx.</em></p> </body> </html> - 100% |

********************************

| 615 0:00:00 ETA written to stdout

Enter fullscreen mode

Exit fullscreen mode

Yay! That’s pretty neat.

Bridge is already the default network

Just for a matter of saving time, whenever we create containers, they are automatically added to the bridge network.

$

docker run

--name

nginx

-d

nginx

$

docker run alpine sh

-c

"wget http://172.17.0.2 -O -"

Enter fullscreen mode

Exit fullscreen mode

It. Just. Works. How cool is that?

Using the container name instead of IP Address

But remembering the IP Address isn’t always good, right? How about using the container name instead of its IP?

$

docker run

--network

bridge alpine sh

-c

"wget http://nginx -O -"

wget: bad address

'nginx'

Enter fullscreen mode

Exit fullscreen mode

Uh oh :(, the bridge network does not resolve names to IP addresses.

But today, is our lucky day. Docker allows us to use the bridge driver and create user-defined custom networks, as easy as doing:

$

docker network create saturno NETWORK ID NAME DRIVER SCOPE 5c65c2b3031b bridge bridge

local

cf446ef29441 host host

local

50fd86384bb9 none null

local

4216c3a16815 saturno bridge

local

Enter fullscreen mode

Exit fullscreen mode

The saturno network uses the same driver bridge used by the bridge default network. If we inspect the network using docker network inspect saturno, we can see that it has no containers yet and uses the Gateway IP 172.18.0.1.

Let’s create containers on the saturno network:

$

docker run

--name

nginx

--network

saturno

-d

nginx

Enter fullscreen mode

Exit fullscreen mode

By inspecting the network again, the container has got the IP 172.18.0.2. So let’s hit the container again:

$

docker run

--network

saturno alpine sh

-c

"wget http://172.18.0.2 -O -"

Enter fullscreen mode

Exit fullscreen mode

It works. Still we want to check using its name instead:

$

docker run

--network

saturno alpine sh

-c

"wget http://nginx -O -"

Enter fullscreen mode

Exit fullscreen mode

What a wonderful day, it works!

The host network

This network is useful when we need to expose the container to the host network.

$

docker

--name

nginx

--network

host

-d

nginx

Enter fullscreen mode

Exit fullscreen mode

$

docker network inspect host

[

{

"Name"

:

"host"

,

"Id"

:

"cf446ef29441aeaaee2a40cfcf9ad120aedb7c51cf2dbc20cc23e567101d217c"

,

"Created"

:

"2022-01-13T21:57:00.2326735Z"

,

"Scope"

:

"local"

,

"Driver"

:

"host"

,

"EnableIPv6"

:

false

,

"IPAM"

:

{

"Driver"

:

"default"

,

"Options"

: null,

"Config"

:

[]

}

,

"Internal"

:

false

,

"Attachable"

:

false

,

"Ingress"

:

false

,

"ConfigFrom"

:

{

"Network"

:

""

}

,

"ConfigOnly"

:

false

,

"Containers"

:

{

"668c12bbabb8cd28e8cc8666d074fc929214f7b9ddfddfa3d76c8476652c4091"

:

{

"Name"

:

"nginx"

,

"EndpointID"

:

"738ea09ee3d450e9f655440a303a52f219d9ca22fe011eb62dffe7d0351f31de"

,

"MacAddress"

:

""

,

"IPv4Address"

:

""

,

"IPv6Address"

:

""

}

}

,

"Options"

:

{}

,

"Labels"

:

{}

}

]

Enter fullscreen mode

Exit fullscreen mode

We can see the container added to the network, but it has no IP address, which means other containers cannot talk to it, only the host.

# Linux only

$

wget http://localhost

-O

-

Enter fullscreen mode

Exit fullscreen mode

It works.

Note: this network works on Linux only. In Docker Desktop for Mac or Windows, we must use another way to expose containers to the host.

The -p option

Another way to expose containers to the host goes by using the flag -p, which is NOT the host network per se, but it publishes the [container port] to the [host port].

$

docker run

--name

nginx

-p

80:80

-d

nginx

$

wget http://localhost

-O

-

Enter fullscreen mode

Exit fullscreen mode

It works!

The none network

Anytime we need to create a completely isolated container which do not talk to any other containers, we can add it to the none network, which uses the driver null.

$

docker run

--name

nginx

--network

none

-d

nginx

$

docker network inspect none

[

{

"Name"

:

"none"

,

"Id"

:

"50fd86384bb9cc90d953a624a5ab70b869357027d3cdc7ebc9b4043798dd4f6a"

,

"Created"

:

"2022-01-13T21:57:00.224557375Z"

,

"Scope"

:

"local"

,

"Driver"

:

"null"

,

"EnableIPv6"

:

false

,

"IPAM"

:

{

"Driver"

:

"default"

,

"Options"

: null,

"Config"

:

[]

}

,

"Internal"

:

false

,

"Attachable"

:

false

,

"Ingress"

:

false

,

"ConfigFrom"

:

{

"Network"

:

""

}

,

"ConfigOnly"

:

false

,

"Containers"

:

{

"90a6691b818e164bd2e1f67e8f3a62ce71eaddbe9ac215c370a8a6766204a2b0"

:

{

"Name"

:

"nginx"

,

"EndpointID"

:

"0ed9e33f051f2df2c37b96fc2fdf7df074b73359117a12a81ae4c28ef0ec6877"

,

"MacAddress"

:

""

,

"IPv4Address"

:

""

,

"IPv6Address"

:

""

}

}

,

"Options"

:

{}

,

"Labels"

:

{}

}

]

Enter fullscreen mode

Exit fullscreen mode

This network has no Gateway IP hence do not associate IP addresses to containers.

Bonus points: connecting containers on-the-fly

Sometimes we already have running containers in the default network but for some reason we want to communicate using name.

$

docker run

--name

nginx

-d

nginx

$

docker run

--name

ruby

-dit

ruby irb

Enter fullscreen mode

Exit fullscreen mode

They live in the default bridge network, they can talk to each other using IP but not using their names, remember?

$

docker network create saturno

Enter fullscreen mode

Exit fullscreen mode

The most obvious solution: first stop them, then create new ones using the saturno network. Correct?

Kinda. But there’s no need to stop the containers! Just connect them to existing networks:

How about:

$

docker network connect saturno nginx

$

docker network connect saturno ruby

$

docker network inspect saturno

"Containers"

:

{

"15bcd3a425024c627a57bddb878a11fcd3f43cb4da4576ef05d89b45a96f49ad"

:

{

"Name"

:

"nginx"

,

"EndpointID"

:

"e0ef0bb83b1e553215cf24dcc6c20355a5ca5367e2d02f120b00b4a77c975964"

,

"MacAddress"

:

"02:42:ac:13:00:02"

,

"IPv4Address"

:

"172.19.0.2/16"

,

"IPv6Address"

:

""

}

,

"6ab4256e8c808ebd16f2e9643e5636df03f58dbfc4778a939df0b286b829babd"

:

{

"Name"

:

"ruby"

,

"EndpointID"

:

"ab3a590379938f8654a0aada7cfab97cc47eb92f3fe89656a2feccc9bd52cbe1"

,

"MacAddress"

:

"02:42:ac:13:00:03"

,

"IPv4Address"

:

"172.19.0.3/16"

,

"IPv6Address"

:

""

}

}

,

Enter fullscreen mode

Exit fullscreen mode

Oh my goodness, it made my day!

Conclusion

This post was a try to explain the main aspects of Docker networking. I hope it helps you to understand a bit more and use it at your needs.

The Docker official website contains a great introduction to networking as well easy tutorials.