Container linking provides a means for one container to easily communicate with another container on the same host. As we’ve seen in previous examples, most container-to-container communication has occurred through IP addresses. Container linking improves on this by allowing linked containers to communicate with each other by name. In addition to providing basic name resolution, it also provides a means to see what services a linked container is providing. In this recipe, we’ll review how to create container links as well as discuss some of their limitations.
In this recipe, we’ll be demonstrating the configuration on a single Docker host. It is assumed that this host has Docker installed and that Docker is in its default configuration. We’ll be altering name resolution settings on the host, so you’ll need root-level access.
The phrase container linking might imply to some that it involves some kind of network configuration or modification. In reality, container linking has very little to do with container networking. In the default mode, container linking provides a means for one container to resolve the name of another. For instance, let’s start two containers on our lab host docker1
:
root@docker1:~# docker run -d -P --name=web1 jonlangemak/web_server_1 88f9c862966874247c8e2ba90c18ac673828b5faac93ff08090adc070f6d2922 root@docker1:~# docker run -d -P --name=web2 --link=web1 jonlangemak/web_server_2 00066ea46367c07fc73f73bdcdff043bd4c2ac1d898f4354020cbcfefd408449 root@docker1:~#
Notice how, when I started the second container, I used a new flag named --link
and referenced the container web1
. We would now say that web2
was linked to web1
. However, they’re not really linked in any sort of way. A better description might be to say that web2
is now aware of web1
. Let’s connect to the container web2
to show you what I mean:
root@docker1:~# docker exec -it web2 /bin/bash root@00066ea46367:/# ping web1 -c 2 PING web1 (172.17.0.2): 48 data bytes 56 bytes from 172.17.0.2: icmp_seq=0 ttl=64 time=0.163 ms 56 bytes from 172.17.0.2: icmp_seq=1 ttl=64 time=0.092 ms --- web1 ping statistics --- 2 packets transmitted, 2 packets received, 0% packet loss round-trip min/avg/max/stddev = 0.092/0.128/0.163/0.036 ms root@00066ea46367:/#
It appears that the web2
container is now able to resolve the container web1
by name. This is because the linking process inserted records into the web2
container's hosts
file:
root@00066ea46367:/# more /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.2 web1 88f9c8629668
172.17.0.3 00066ea46367
root@00066ea46367:/#
With this configuration, the web2
container can reach the web1
container either by the name we gave the container at runtime (web1
) or the unique hostname
Docker generated for the container (88f9c8629668
).
In addition to the hosts
file being updated, web2
also generates some new environmental variables:
root@00066ea46367:/# printenv WEB1_ENV_APACHE_LOG_DIR=/var/log/apache2 HOSTNAME=00066ea46367 APACHE_RUN_USER=www-data WEB1_PORT_80_TCP=tcp://172.17.0.2:80 WEB1_PORT_80_TCP_PORT=80 LS_COLORS= WEB1_PORT=tcp://172.17.0.2:80 WEB1_ENV_APACHE_RUN_GROUP=www-data APACHE_LOG_DIR=/var/log/apache2 PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin PWD=/ WEB1_PORT_80_TCP_PROTO=tcp APACHE_RUN_GROUP=www-data SHLVL=1 HOME=/root WEB1_PORT_80_TCP_ADDR=172.17.0.2 WEB1_ENV_APACHE_RUN_USER=www-data WEB1_NAME=/web2/web1 _=/usr/bin/printenv root@00066ea46367:/#
You’ll notice many new environmental variables. Docker will copy any environmental variables from the linked container that were defined as part of the container. This includes:
ENV
variables from the images Dockerfile--env
or -e
flagIn this case, these three variables were defined as ENV
variables in the image's Dockerfile:
APACHE_RUN_USER=www-data APACHE_RUN_GROUP=www-data APACHE_LOG_DIR=/var/log/apache2
Because both container images have the same ENV
variables defined, we’ll see the local variables as well as the same environmental variables from the container web1
prefixed with WEB1_ENV_
:
WEB1_ENV_APACHE_RUN_USER=www-data WEB1_ENV_APACHE_RUN_GROUP=www-data WEB1_ENV_APACHE_LOG_DIR=/var/log/apache2
In addition, Docker also created six other environmental variables that describe the web1
container as well as any of its exposed ports:
WEB1_PORT=tcp://172.17.0.2:80 WEB1_PORT_80_TCP=tcp://172.17.0.2:80 WEB1_PORT_80_TCP_ADDR=172.17.0.2 WEB1_PORT_80_TCP_PORT=80 WEB1_PORT_80_TCP_PROTO=tcp WEB1_NAME=/web2/web1
Linking also allows you to specify aliases. For instance, let’s stop, remove, and respawn container web2
using a slightly different syntax for linking:
user@docker1:~$ docker stop web2
web2
user@docker1:~$ docker rm web2
web2
user@docker1:~$ docker run -d -P --name=web2 --link=web1:webserver
jonlangemak/web_server_2
e102fe52f8a08a02b01329605dcada3005208d9d63acea257b8d99b3ef78e71b
user@docker1:~$
Notice that, after the link definition, we inserted a :webserver.
The name after the colon represents the alias for the link. In this case, I’ve specified an alias for the container web1
as webserver
.
If we examine the web2
container, we’ll see that the alias is now also listed in the hosts
file:
root@c258c7a0884d:/# more /etc/hosts
…<Additional output removed for brevity>…
172.17.0.2 webserver 88f9c8629668 web1
172.17.0.3 c258c7a0884d
root@c258c7a0884d:/#
Aliases also impact the environmental variables created during linking. Rather than using the container name, they’ll instead use the alias:
user@docker1:~$ docker exec web2 printenv …<Additional output removed for brevity>… WEBSERVER_PORT_80_TCP_ADDR=172.17.0.2 WEBSERVER_PORT_80_TCP_PORT=80 WEBSERVER_PORT_80_TCP_PROTO=tcp …<Additional output removed for brevity>… user@docker1:~$
At this point, you might be wondering how dynamic this is. After all, Docker is providing this functionality by updating static files in each container. What happens if a container’s IP address changes? For instance, let’s stop the container web1
and start a new container named web3
using the same image:
user@docker1:~$ docker stop web1 web1 user@docker1:~$ docker run -d -P --name=web3 jonlangemak/web_server_1 69fa80be8b113a079e19ca05c8be9e18eec97b7bbb871b700da4482770482715 user@docker1:~$
If you’ll recall from earlier, the container web1
had an IP address of 172.17.0.2
allocated to it. Since I stopped the container, Docker will release that IP address reservation making it available to be reassigned to the next container we start. Let’s check the IP address assigned to the container web3
:
user@docker1:~$ docker exec web3 ip addr show dev eth0 79: eth0@if80: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc noqueue state UP link/ether 02:42:ac:11:00:02 brd ff:ff:ff:ff:ff:ff inet 172.17.0.2/16 scope global eth0 valid_lft forever preferred_lft forever inet6 fe80::42:acff:fe11:2/64 scope link valid_lft forever preferred_lft forever user@docker1:~$
As expected, web3
took the now open IP address of 172.17.0.2
that previously belonged to the web1
container. We can also verify that the container web2
still believes that this IP address belongs to the web1
container:
user@docker1:~$ docker exec –t web2 more /etc/hosts | grep 172.17.0.2
172.17.0.2 webserver 88f9c8629668 web1
user@docker1:~$
If we start the container web1
once again, we should see that it will get a new IP address allocated to it:
user@docker1:~$ docker start web1 web1 user@docker1:~$ docker exec web1 ip addr show dev eth0 81: eth0@if82: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc noqueue state UP link/ether 02:42:ac:11:00:04 brd ff:ff:ff:ff:ff:ff inet 172.17.0.4/16 scope global eth0 valid_lft forever preferred_lft forever inet6 fe80::42:acff:fe11:4/64 scope link valid_lft forever preferred_lft forever user@docker1:~$
If we check the container web2
again, we should see that Docker has updated it to reference the web1
container’s new IP address:
user@docker1:~$ docker exec web2 more /etc/hosts | grep web1 172.17.0.4 webserver 88f9c8629668 web1 user@docker1:~$
However, while Docker takes care of updating the hosts
file with the new IP address, it will not take care of updating any of the environmental variables to reflect the new IP address:
user@docker1:~$ docker exec web2 printenv …<Additional output removed for brevity>… WEBSERVER_PORT=tcp://172.17.0.2:80 WEBSERVER_PORT_80_TCP=tcp://172.17.0.2:80 WEBSERVER_PORT_80_TCP_ADDR=172.17.0.2 …<Additional output removed for brevity>… user@docker1:~$
In addition, it should be pointed out that the link is only one way. That is, this link does not cause the container web1
to become aware of the web2
container. Web1
will not receive the host records or the environmental variables referencing the web2
container:
user@docker1:~$ docker exec -it web1 ping web2 ping: unknown host user@docker1:~$
Another reason to provision links is when you use Docker Inter-Container Connectivity (ICC) mode set to false
. As we’ve discussed previously, ICC prevents any containers on the same bridge from talking directly to each other. This forces them to talk to each other only though published ports. Linking provides a mechanism to override the default ICC rules. To demonstrate, let’s stop and remove all the containers on our host docker1
and then add the following Docker option to the systemd drop-in file:
ExecStart=/usr/bin/dockerd --icc=false
Now reload the systemd configuration, restart the service, and start the following containers:
docker run -d -P --name=web1 jonlangemak/web_server_1 docker run -d -P --name=web2 jonlangemak/web_server_2
With ICC mode on, you’ll notice that containers can’t talk directly to each other:
user@docker1:~$ docker exec web1 ip addr show dev eth0 87: eth0@if88: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc noqueue state UP link/ether 02:42:ac:11:00:02 brd ff:ff:ff:ff:ff:ff inet 172.17.0.2/16 scope global eth0 valid_lft forever preferred_lft forever inet6 fe80::42:acff:fe11:2/64 scope link valid_lft forever preferred_lft forever user@docker1:~$ docker exec -it web2 curl http://172.17.0.2 user@docker1:~$
In the preceding example, web2
is not able to access the web servers on web1
. Now, let’s delete and recreate the web2
container, this time linking it to web1
:
user@docker1:~$ docker stop web2 web2 user@docker1:~$ docker rm web2 web2 user@docker1:~$ docker run -d -P --name=web2 --link=web1 jonlangemak/web_server_2 4c77916bb08dfc586105cee7ae328c30828e25fcec1df55f8adba8545cbb2d30 user@docker1:~$ docker exec -it web2 curl http://172.17.0.2 <body> <html> <h1><span style=”color:#FF0000;font-size:72px;”>Web Server #1 - Running on port 80</span> </h1> </body> </html> user@docker1:~$
We can see that, with the link in place, the communication is allowed as expected. Once again, just like the link, this access is allowed in one direction.
It should be noted that linking works differently when using user-defined networks. In this recipe, we covered what are now being named legacy links. Linking with user-defined networks will be covered in the next two recipes.