DEV Community

Cover image for Mastering the Docker networking
Leandro Proença
Leandro Proença

Posted on • Updated on

Mastering the Docker networking

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.

Discussion (9)

Collapse
willb profile image
willb

Great article!
I've got a question - especially as networking is not a strength of mine.
Trying to figure out the difference between the bridge network and the saturno network. Why does the saturno network allow you to use the container name, where the bridge network doesn't.
I inspected both, and it seems the only difference are the options. Which one of the options for the bridge network, disallows the use of the container name?

Collapse
leandronsp profile image
Leandro Proença Author

I'm not aware of the internals but I guess that the default bridge network does not add containers to the /etc/hosts

Collapse
willb profile image
willb

Thanks!

Collapse
benbb96 profile image
BBB

I was wondering the same thing

Collapse
sabberworm profile image
Raphael Schweikert

Thanks for the writeup. What I don’t understand is how containers connect to the internet. From what I know, it would involve some sort of NAT, at least for IPv4. On that tangent, I would love to know how to use IPv6 (for internet connectivity) on a container without automatically exposing IPv6-bound ports to the outside (and without having to configure complicated firewall rules). It’s kinda weird that docker doesn’t treat IPv4 and IPv6 the same in this regard.

Collapse
jleonardolemos profile image
Leonardo Lemos

Nice post thanks!!!! What about overlay networks, they are very hard to understand and a post like that would be very nice??

Collapse
leandronsp profile image
Leandro Proença Author

Indeed, thanks for the feedback, I'll try to write about overlay networks!

Collapse
hamzaely profile image
Hamza EL YAAQOUBI

Thank you !

Collapse
hdjumscom profile image
HDJumsCom

great post. thanks man