Docker

Configuring DNS in Docker

1. Introduction

This post introduces Docker Engine’s network feature in general and specifically introduces configuring DNS in containers. This post assumes that you have the Docker Engine installed and that you know the basics of working with containers.

We will not discuss service discovery from other networks here since that needs a deeper knowledge of Docker tools like Docker Swarm. Since these posts focus on the basics of Docker we will focus on networking cotainers within the same host. So let’s get started with understanding the basics of networking in Docker.

2. Basics of Networking Configurations in Docker Engine

The Docker Engine provides 3 networks by default – bridge, host and none. The command docker network ls lists out all networks created by Docker. So on a default installation you will see something like this.

$ docker network ls
NETWORK ID          NAME                DRIVER              SCOPE
36d51e62e518        bridge              bridge              local               
18e8e68644ea        host                host                local               
1f87f168df62        none                null                local

The bridge network is mapped to the docker0 network bridge on the Docker Engine’s host. The ip address command can be used to check the details of docker0.

$ ip address show label docker0
7: docker0: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc noqueue state UP 
    link/ether 02:45:01:c0:ba:c7 brd ff:ff:ff:ff:ff:ff
    inet 172.17.0.1/16 scope global docker0
       valid_lft forever preferred_lft forever
    inet6 fe80::42:1ff:fec0:bac7/64 scope link 
       valid_lft forever preferred_lft forever

2.1. Inspect Networks

Every container created by the Docker engine is added to the default bridge network. The command docker network inspect can be used to inspect the network like so…

$ docker network inspect bridge
[
    {
        "Name": "bridge",
        "Id": "36d51e62e518d5430c798ce6eb68dc2737e9ad0555dd45097b04ef7597bce644",
        "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,
        "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": {}
    }
]

Note the "Containers" section in the above picture. It lists the containers attached to the bridge network. This section will be empty in a fresh Docker installation or when all containers are stopped.

2.2. Create User Defined Networks

Apart from the default networks, users can create new networks with the docker network create command. Let us create a new network called example-network and inspect it’s contents now.

$ docker network create example-network && docker network inspect example-network
301ffd3fbc1e323d40d2bc687f2e5c6341c16011e91d57151577fa08c914a157
[
    {
        "Name": "example-network",
        "Id": "301ffd3fbc1e323d40d2bc687f2e5c6341c16011e91d57151577fa08c914a157",
        "Scope": "local",
        "Driver": "bridge",
        "EnableIPv6": false,
        "IPAM": {
            "Driver": "default",
            "Options": {},
            "Config": [
                {
                    "Subnet": "172.18.0.0/16",
                    "Gateway": "172.18.0.1/16"
                }
            ]
        },
        "Internal": false,
        "Containers": {},
        "Options": {},
        "Labels": {}
    }
]

Let us now add a container to this network and inspect again. The command docker run --network= can be used to attach a container to a network of our choice, like below:

$ docker run -itd --name=infinite_loop --network=example-network enhariharan/infinite-loop
8da6157240e09d69c7490c1af4ce483b30ff6f7ba7804b45818c0975e7b8ba25

The container is added into the network example-network. Let us now investigate the network settings again.

$ docker network inspect example-network
[
    {
        "Name": "example-network",
        "Id": "301ffd3fbc1e323d40d2bc687f2e5c6341c16011e91d57151577fa08c914a157",
        "Scope": "local",
        "Driver": "bridge",
        "EnableIPv6": false,
        "IPAM": {
            "Driver": "default",
            "Options": {},
            "Config": [
                {
                    "Subnet": "172.18.0.0/16",
                    "Gateway": "172.18.0.1/16"
                }
            ]
        },
        "Internal": false,
        "Containers": {
            "8da6157240e09d69c7490c1af4ce483b30ff6f7ba7804b45818c0975e7b8ba25": {
                "Name": "infinite_loop",
                "EndpointID": "ccb0318b24dc3b0f1676f5aa16e5ea839adb715baa6bcd4ad16cdadb4aabd973",
                "MacAddress": "02:42:ac:12:00:02",
                "IPv4Address": "172.18.0.2/16",
                "IPv6Address": ""
            }
        },
        "Options": {},
        "Labels": {}
    }
]

You can see that the "Containers" section now has the container infinite_loop created from the image.

2.3. Connect Containers Within a Network

All containers are added to the bridge network by default. Containers within the same Docker network can connect to each other. Let us see that through a simple example. We will create 2 simple containers from the image enhariharan/infinite-loop into the custom network example-network created earlier. Then we’ll inspect the network to see that the containers were added as expected.

$ docker run -itd --name=infinite_loop_1 --network=example-network enhariharan/infinite-loop
3c23fb845274e24ac8bdaa3216affc3b6d2755b58e6a8a4424c4008ca3c8022c
$
$ docker run -itd --name=infinite_loop_2 --network=example-network enhariharan/infinite-loop
9bd946658d4456618f60413e0cb41f6b4502eb60cfbff325a1bd7f218bffdd45
$
$ docker network inspect example-network
...
       "Containers": {
            "3c23fb845274e24ac8bdaa3216affc3b6d2755b58e6a8a4424c4008ca3c8022c": {
                "Name": "infinite_loop_1",
                "EndpointID": "0c5fc22252e5d88ae746fb07d9dc9827ab6840a40db956eff2ed8bcfa042a099",
                "MacAddress": "02:42:ac:12:00:02",
                "IPv4Address": "172.18.0.2/16",
                "IPv6Address": ""
            },
            "9bd946658d4456618f60413e0cb41f6b4502eb60cfbff325a1bd7f218bffdd45": {
                "Name": "infinite_loop_2",
                "EndpointID": "1eecb3c8414026bfeaf63cfb23fb5319ca068c2c3c220ad486f19de02b2225bd",
                "MacAddress": "02:42:ac:12:00:03",
                "IPv4Address": "172.18.0.3/16",
                "IPv6Address": ""
            }
        },
...

Containers infinite_loop_1 and infinite_loop_2 were created in detached mode. They have been added into example-network with IP address 172.18.0.2 and 172.18.0.3 respectively, as confirmed above. If the --network= is not provided then containers are added to the bridge network by default. Now, let us open a session into the container infinite_loop_1 and try to ping infinite_loop_2.

$ docker exec -it infinite_loop_1 sh
/ # ping -c 5 172.18.0.3
PING 172.18.0.3 (172.18.0.3): 56 data bytes
64 bytes from 172.18.0.3: seq=0 ttl=64 time=0.165 ms
64 bytes from 172.18.0.3: seq=1 ttl=64 time=0.124 ms
64 bytes from 172.18.0.3: seq=2 ttl=64 time=0.115 ms
64 bytes from 172.18.0.3: seq=3 ttl=64 time=0.115 ms
64 bytes from 172.18.0.3: seq=4 ttl=64 time=0.116 ms

--- 172.18.0.3 ping statistics ---
5 packets transmitted, 5 packets received, 0% packet loss
round-trip min/avg/max = 0.106/0.117/0.137 ms

So as you can see, containers added into the same network can automatically find each other through their IP addresses. Next, let us see how to setup DNS in Docker so that we need use the IP addresses to communicate.

3. Setup Container DNS in Bridge Network

Docker engine uses these 3 files within every container to configure it’s DNS.

  • /etc/hostname – This file maps the container IP address to a name.
  • /etc/hosts – This file maps other container’s IP addresses to names.
  • /etc/resolv.conf – This file contains the IP addresses of other DNS servers to refer to if the container cannot resolve a name to IP address.

These files can be setup and manipulated using the docker run command. The hostname for a container is set into /etc/hostname using the --hostname= option.

$ docker run -itd --name=infinite_loop_1 --hostname=container1 enhariharan/infinite-loop
986d6b95a283493edd6d331d3c9f0c31595b9ec2b4ffae737ad99f5b1b090c46
$
$ docker exec -it infinite_loop_1 cat /etc/hostname
container1

The entry of container1 can be put into the /etc/hosts file of container2 using the --link= option of docker run. Let us try one more example now, we will link infinite_loop_1 to infinite_loop_2 and ping container1 from within container2 using the hostname of infinite_loop_1.

$ docker run -itd --name=infinite_loop_1 --hostname=container1 enhariharan/infinite-loop
c97fe6577f1e8b2ca90b04157a299c45becadea5a5ccd61564c8d279d98c3719
$
$ docker run -itd --name=infinite_loop_2 --hostname=container2 --link=infinite_loop_1 enhariharan/infinite-loop
2a26adfae9fb06038a823e4fea9a5118875abbdb8e023502eda96c91d4c79d6c
$
$ docker exec -it infinite_loop_2 cat /etc/hosts
127.0.0.1	localhost
::1	localhost ip6-localhost ip6-loopback
fe00::0	ip6-localnet
ff00::0	ip6-mcastprefix
ff02::1	ip6-allnodes
ff02::2	ip6-allrouters
172.17.0.3	infinite_loop_1 container1
172.17.0.4	container2
$
$ docker exec -it infinite_loop_2 ping -c 5 container1
PING container1 (172.17.0.3): 56 data bytes
64 bytes from 172.17.0.3: seq=0 ttl=64 time=0.175 ms
64 bytes from 172.17.0.3: seq=1 ttl=64 time=0.118 ms
64 bytes from 172.17.0.3: seq=2 ttl=64 time=0.115 ms
64 bytes from 172.17.0.3: seq=3 ttl=64 time=0.123 ms
64 bytes from 172.17.0.3: seq=4 ttl=64 time=0.118 ms

--- container1 ping statistics ---
5 packets transmitted, 5 packets received, 0% packet loss
round-trip min/avg/max = 0.115/0.129/0.175 ms

But the fun with discovering containers in the bridge network sort of stops here. To connect to containers without using their IP addreses and without linking to them at docker run as above we need to do user-defined networks of Docker. Let us see the basics of that next.

4. Setup Container DNS in a User Defined Network

There are some important differences between connecting containers in user-defined networks and within bridge network. One such point is that the --link option of docker run will not work in user-defined networks. Let us see how we can get the same functionality as above using user-defined networks. We will add the containers infinite_loop_1 and infinite_loop_2 into a user-defined network called example_network and ping infinite_loop_2 from within infinite_loop_1.

$ docker run -itd --name=infinite_loop_1 --hostname=container1 --network=example_network enhariharan/infinite-loop
d91757ae6e4dab01d9a37e73ec9a314c15ed195e5763fb6d9898ceb45dbc0964
$
$ docker run -itd --name=infinite_loop_2 --hostname=container2 --network=example_network enhariharan/infinite-loop
3ab4dad71bad7114025db7330686cb23114c76f705f9e3489a2763aa74970f2b
$
$ docker exec -it infinite_loop_2 ping -c 4 infinite_loop_1
PING infinite_loop_1 (172.20.0.2): 56 data bytes
64 bytes from 172.20.0.2: seq=0 ttl=64 time=0.111 ms
64 bytes from 172.20.0.2: seq=1 ttl=64 time=0.122 ms
64 bytes from 172.20.0.2: seq=2 ttl=64 time=0.119 ms
64 bytes from 172.20.0.2: seq=3 ttl=64 time=0.131 ms

--- infinite_loop_1 ping statistics ---
4 packets transmitted, 4 packets received, 0% packet loss
round-trip min/avg/max = 0.111/0.120/0.131 ms
$
$ docker exec -it infinite_loop_2 ping -c 4 container1
ping: bad address 'container1'

From the above snippet, it is seen that containers are visible in the same network by their container names but not their host names since ping by the hostname container1 does not work.

The recommended way is to add a network-alias to each container apart from the hostname. This network-alias can then be used to connect by other containers. Let us try the above steps again but by setting a network-alias this time.

$ docker run -itd --name=infinite_loop_1 --hostname=container1 --network=example_network --network-alias=container1_alias enhariharan/infinite-loop
c80675335e3a235b84e317d5a160324e7add9421d197524a26ef2c461a679f1c
$
$ docker run -itd --name=infinite_loop_2 --hostname=container2 --network=example_network --network-alias=container2_alias enhariharan/infinite-loop
532b2d3d2694ed41c3abe44ae32671619b312b9a5c33604f8eaacd620b1d2874
$
$ docker exec -it infinite_loop_2 ping -c 4 container1_alias
PING container1_alias (172.20.0.2): 56 data bytes
64 bytes from 172.20.0.2: seq=0 ttl=64 time=0.127 ms
64 bytes from 172.20.0.2: seq=1 ttl=64 time=0.115 ms
64 bytes from 172.20.0.2: seq=2 ttl=64 time=0.119 ms
64 bytes from 172.20.0.2: seq=3 ttl=64 time=0.113 ms

--- container1_alias ping statistics ---
4 packets transmitted, 4 packets received, 0% packet loss
round-trip min/avg/max = 0.113/0.118/0.127 ms

So this is how Docker Engine’s embedded DNS can be configured so that containers can communicate with other containers within the same host and within the same network bridge. To talk among other networks or to talk among other hosts an overlay network must be configured. Also, a cluster of Docker hosts can be created using tools like Docker Swarm. These topics are outside the scope of this post and will be taken up in a future post.

5. Summary

In this post, we were introduced to the basics of Docker networking.  We learned the types of networks supported and how to create a user-defined bridge type network. We also understand how to add containers into specific networks. Then we understood how containers can communicate with one another by configuring the embedded DNS server provided by Docker Engine.

Hariharan Narayanan

Hari graduated from the School of Computer and Information Sciences in the University of Hyderabad. Over his career he has been involved in many complex projects in mobile applications, enterprise applications, distributed applications, micro-services, and other platforms and frameworks. He works as a consultant and is mainly involved with projects based on Java, C++ and Big Data technologies.
Subscribe
Notify of
guest

This site uses Akismet to reduce spam. Learn how your comment data is processed.

0 Comments
Oldest
Newest Most Voted
Inline Feedbacks
View all comments
Back to top button