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.