get containers to talk to each other

By | September 9, 2020

Container ports

We saw in the last blog we were using -p 8080:80. The -p parameter specifies which port to expose through the docker firewall.

docker container run -p 8081:80 --name web1 -d  nginx
b0e89fba02a1783b893c96884f4fb6d09a1ce7ded1da4f15c2052497fd42a517

You can see how the port resolves using docker container port web1

docker container port web1
80/tcp -> 0.0.0.0:8081

To get the ip address of your container use docker container inspect --format '{{ .NetworkSettings.IPAddress }}' web1 . In my case it returned 172.17.0.3

docker container inspect --format '{{ .NetworkSettings.IPAddress }}' web1 
172.17.0.2

Next find out the ip address of your computer by entering ifconfig en0. You will notice your computer will have a totally different address. Why is that? This is because docker creates its own subnet to communicate internally. It typically starts around 172.17.0.0/16.

ifconfig en0
en0: flags=8863<UP,BROADCAST,SMART,RUNNING,SIMPLEX,MULTICAST> mtu 1500
	options=400<CHANNEL_IO>
	ether 14:7d:da:2a:4d:87 
	inet6 fe80::1c13:1187:ea99:30bd%en0 prefixlen 64 secured scopeid 0x6 
	inet6 2601:14b:c203:70f0:1447:98e5:231:d7e1 prefixlen 64 autoconf secured 
	inet6 2601:14b:c203:70f0:e455:cc16:6496:96a0 prefixlen 64 autoconf temporary 
	inet 192.168.0.12 netmask 0xffffff00 broadcast 192.168.0.255
	nd6 options=201<PERFORMNUD,DAD>
	media: autoselect
	status: active

Docker network

Let’s start playing around with the docker network command. You can find more information on it here.

To see all the current networks in your docker domain enter docker network ls . You will see the following three networks. For now we will concentrate on the bridge network because it is the primary network docker uses to communicate.

docker network ls   
NETWORK ID          NAME                DRIVER              SCOPE
1c3606691a98        bridge              bridge              local
580f45b6483b        host                host                local
41a131f71728        none                null                local

You can create your own network by entering in docker network create my_network. Docker will automatically add a new bridge network and assign it to the next subnet in 172 domain. Now when you enter docker network ls you will see four networks.

docker network create my_network
78d36f33a81dfda4405214fc2374955a5500f1f04f09039999f5bfe5c50a9b19
docker network ls
NETWORK ID          NAME                DRIVER              SCOPE
8a6c504db3f0        bridge              bridge              local
580f45b6483b        host                host                local
78d36f33a81d        my_network          bridge              local
41a131f71728        none                null                local

Using this network, add two new web servers by entering docker container run -d --name web2 --network my_network nginx and docker container run -d --name web3 --network my_network nginx.

docker container run -d --name web2 --network my_network nginx
911b44bfc5a040d009d0b977a19be9bf353586aa3536a407c93d075b184c56c1
docker container run -d --name web3 --network my_network nginx
d711016b726e0fcc92b23cf884450bce3ebb19838f05eb48d0f9fa734ef42df4

To see everything that we have created enter docker network inspect bridge and docker network inspect my_network. You will now see that there are two subnets 172.17.0.0/16 and 172.19.0.0/16. The first one has the web1 container and the second one has web2 and web3 containers.

docker network inspect bridge
[
    {
        "Name": "bridge",
        "Id": "8a6c504db3f0209c92d08b246c6da126f503a37a8f01e46c0eb03e2db3e2b3fa",
        "Created": "2020-09-09T17:28:13.939935794Z",
        "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": {
            "b0e89fba02a1783b893c96884f4fb6d09a1ce7ded1da4f15c2052497fd42a517": {
                "Name": "web1",
                "EndpointID": "3acbb0afb268b9b9da8d54b713605470ab34b1153f48cdfef99790e5990f0771",
                "MacAddress": "02:42:ac:11:00:02",
                "IPv4Address": "172.17.0.2/16",
                "IPv6Address": ""
            }
        },
        "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": {}
    }
]
docker network inspect my_network
[
    {
        "Name": "my_network",
        "Id": "78d36f33a81dfda4405214fc2374955a5500f1f04f09039999f5bfe5c50a9b19",
        "Created": "2020-09-10T01:16:47.0666841Z",
        "Scope": "local",
        "Driver": "bridge",
        "EnableIPv6": false,
        "IPAM": {
            "Driver": "default",
            "Options": {},
            "Config": [
                {
                    "Subnet": "172.19.0.0/16",
                    "Gateway": "172.19.0.1"
                }
            ]
        },
        "Internal": false,
        "Attachable": false,
        "Ingress": false,
        "ConfigFrom": {
            "Network": ""
        },
        "ConfigOnly": false,
        "Containers": {
            "911b44bfc5a040d009d0b977a19be9bf353586aa3536a407c93d075b184c56c1": {
                "Name": "web2",
                "EndpointID": "196dd5d1bf553ee6757e837a7ed8a002dc061ba07814f6b115a422e26f849b84",
                "MacAddress": "02:42:ac:13:00:02",
                "IPv4Address": "172.19.0.2/16",
                "IPv6Address": ""
            },
            "d711016b726e0fcc92b23cf884450bce3ebb19838f05eb48d0f9fa734ef42df4": {
                "Name": "web3",
                "EndpointID": "8522f2d18a2bd79926a5de4820bf42392b14f5b8ed83b6a191d5e0c42d40eab5",
                "MacAddress": "02:42:ac:13:00:03",
                "IPv4Address": "172.19.0.3/16",
                "IPv6Address": ""
            }
        },
        "Options": {},
        "Labels": {}
    }
]

Web1 cannot talk to the containers in my_network. You can connect it to my_network by entering network connect [networkId] [containerId]

docker network connect my_network web1

Now when you inspect web1 you will see it is connected to two networks.

docker container inspect web1
....
"Networks": {
                "bridge": {
                    "IPAMConfig": null,
                    "Links": null,
                    "Aliases": null,
                    "NetworkID": "8a6c504db3f0209c92d08b246c6da126f503a37a8f01e46c0eb03e2db3e2b3fa",
                    "EndpointID": "3acbb0afb268b9b9da8d54b713605470ab34b1153f48cdfef99790e5990f0771",
                    "Gateway": "172.17.0.1",
                    "IPAddress": "172.17.0.2",
                    "IPPrefixLen": 16,
                    "IPv6Gateway": "",
                    "GlobalIPv6Address": "",
                    "GlobalIPv6PrefixLen": 0,
                    "MacAddress": "02:42:ac:11:00:02",
                    "DriverOpts": null
                },
                "my_network": {
                    "IPAMConfig": {},
                    "Links": null,
                    "Aliases": [
                        "b0e89fba02a1"
                    ],
                    "NetworkID": "78d36f33a81dfda4405214fc2374955a5500f1f04f09039999f5bfe5c50a9b19",
                    "EndpointID": "7ddd9f1f0df7399f9f82e46a91c64d3c06d9a7a18343bc9bc6ba80f17a3e41a1",
                    "Gateway": "172.19.0.1",
                    "IPAddress": "172.19.0.4",
                    "IPPrefixLen": 16,
                    "IPv6Gateway": "",
                    "GlobalIPv6Address": "",
                    "GlobalIPv6PrefixLen": 0,
                    "MacAddress": "02:42:ac:13:00:04",
                    "DriverOpts": {}
                }
            }
....

DNS

When you start and stop containers the ip addresses can change. To addresses this docker has DNS resolution built in. To make this work you have to put named containers in a user created network. Then docker can use our container names to find the ip of the container. This is not true of the default network docker0. So always create your own network and put your containers in it.

To demonstrate the nuances of DNS lets add one more container, web4, by entering docker container run -p 8083:80 --name web4 -d nginx

docker container run -p 8083:80 --name web4 -d  nginx
80d829f37c1d952731b3d017b654a8c1b7e3936ae4872c5fab201c51d07cd6ec

The diagram below shows how all the containers can communicate. They all can talk to the internet but not all of them can communicate with each other. Web4 cannot talk to web1, web2 or web3. Web1 can talk to web2 and web3 because it was added to my user created network. Web3 can talk to web2 because they are in the my user created network.

To demonstrate this let’s install ping on all our servers using the following.

docker container exec -it web1 apt-get update
docker container exec -it web1 apt-get install inetutils-ping
docker container exec -it web2 apt-get update
docker container exec -it web2 apt-get install inetutils-ping
docker container exec -it web3 apt-get update
docker container exec -it web3 apt-get install inetutils-ping
docker container exec -it web4 apt-get update
docker container exec -it web4 apt-get install inetutils-ping

Since web2 and web3 are in the same subnet they can ping one another just using their names. And web1 can ping web2 and web3 even though it is in a totally different network, since we connected them using docker network connect my_network web1. But web1 cannot ping web4 even though its in the same network. This is because the docker default network doesn’t support DNS resolution. So the take away here is to always create your own network and put containers in that.

docker container exec -it web2 ping web3
docker container exec -it web3 ping web2
docker container exec -it web1 ping web2
docker container exec -it web1 ping web4

Next if we disconnect the container from the user created network using docker network disconnect [networkId] [containerId] we will not be able to ping web2 with web1. This demonstrates network isolation which is very useful for restricting access.

docker network disconnect my_network web1
docker container exec -it web1 ping web2 
ping: unknown host

To demonstrate that DNS works we can stop and start our servers in a reverse order which will create different ip address, but they still can be found in our created network. Do this with the following commands.

docker container stop web1
docker container stop web2
docker container stop web3
docker container stop web4
docker container start web4
docker container start web3
docker container start web2
docker container start web1

docker container exec -it web2 ping web3 //works
docker container exec -it web3 ping web2 //works
docker container exec -it web4 ping web1 //fails

In this post we showed how containers can talk to each other. The next post will be on something even more exciting.