Docker network connectivity up until this point has relied on exposing individual services hosted in a container to the physical network. However, what if you want to expose a service from one container to another without exposing it to the Docker host? In this recipe we'll walk through how to map services between two containers running on the same Docker host.
You'll need access to a Docker host and an understanding of how your Docker host is connected to the network. In this recipe, we'll be using the docker1
host that we used in previous recipes. You'll want to make sure that you have access to view iptables
rules to verify netfilter policies. If you wish to download and run example containers, your Docker host will also need access to the Internet. In some cases, the changes we make may require you to have root-level access to the system.
Mapping services from one container to another is sometimes referred to as mapped container mode. Mapped container mode allows you to start a container that utilizes an existing, or primary, container's network configuration. That is, a mapped container will use the same IP and port configuration as the primary container. For the sake of example, let's consider running the following container:
user@docker1:~$ docker run --name web4 -d -P jonlangemak/web_server_4_redirect
Running this container starts the container in bridge mode and attaches it to the docker0
bridge as we would expect.
The topology will look pretty standard at this point, something like what is shown in the following topology:
Now run a second container on the same host, but this time specify that the network should be that of the primary container web4
:
user@docker1:~$ docker run --name web3 -d --net=container:web4
jonlangemak/web_server_3_8080
Our topology now looks as follows:
Note how the container web3
is now depicted as being attached directly to web4
rather than to the docker0
bridge. By looking at the networking configuration of each container, we can validate that this is actually the case:
user@docker1:~$ docker exec web4 ip addr show 1: lo: <LOOPBACK,UP,LOWER_UP> mtu 65536 qdisc noqueue state UNKNOWN qlen 1 link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00 inet 127.0.0.1/8 scope host lo valid_lft forever preferred_lft forever inet6 ::1/128 scope host valid_lft forever preferred_lft forever 16: eth0@if17: <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:~$ user@docker1:~$ docker exec web3 ip addr show 1: lo: <LOOPBACK,UP,LOWER_UP> mtu 65536 qdisc noqueue state UNKNOWN qlen 1 link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00 inet 127.0.0.1/8 scope host lo valid_lft forever preferred_lft forever inet6 ::1/128 scope host valid_lft forever preferred_lft forever 16: eth0@if17: <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 we can see, the interfaces are identical both in IP configuration as well as MAC addresses. Using the syntax of --net:container<container name/ID>
in the docker run
command joins the new container to the same network construct that the referenced container is in. This means that the mapped container has the same network configuration as the primary container.
There is a limitation to this configuration that is worth noting. A container that joins another container's network cannot publish any of its own ports. So while this means that we can't publish ports of mapped containers to the host, we can consume them locally. Going back to our example, this means that we can't publish port 8080
of container web3
to the host. However, container web4
can consume nonpublished services of the container web3
locally. For instance, each of the containers in this example hosts a web service:
web3
hosts a web server running on port 8080
web4
hosts a web server running on port 80
From an external host perspective, there is no way to access the web service of the container web3
. We can however, access these services through the container web4
. The container web4
is hosting a PHP script named test.php
that pulls the index pages of its own web server as well as that of a web server running on port 8080
. The script is as follows:
<? $page = file_get_contents('http://localhost:80/'); echo $page; $page1 = file_get_contents('http://localhost:8080/'); echo $page1; ?>
The script lives in the web server's root hosting directory (/var/www/
), so we can access the port by browsing to the web4
container's published port followed by test.php
:
user@docker1:~$ docker port web4 80/tcp -> 0.0.0.0:32768 user@docker1:~$ user@docker1:~$ curl http://localhost:32768/test.php <body> <html> <h1><span style="color:#FF0000;font-size:72px;">Web Server #4 - Running on port 80</span> </h1> </body> </html> <body> <html> <h1><span style="color:#FF0000;font-size:72px;">Web Server #3 - Running on port 8080</span> </h1> </body> </html> user@docker1:~$
As you can see, the script is able to pull the index page from both containers. Let's stop the container web3
and run this test again to prove that it's really the one providing this index page response:
user@docker1:~$ docker stop web3 web3 user@docker1:~$ curl http://localhost:32768/test.php <body> <html> <h1><span style="color:#FF0000;font-size:72px;">Web Server #4 - Running on port 80</span> </h1> </body> </html> user@docker1:~$
As you can see, we no longer get the response from the mapped container. Mapped container mode is useful for scenarios where you need to provide a service to an existing container, but don't need to publish any of the mapped container's ports directly to the Docker host or outside network. Although there is a limitation that mapped containers cannot publish any of their own ports, this does not mean we can't publish them ahead of time.
For instance, we could expose port 8080
when we run the primary container:
user@docker1:~$ docker run --name web4 -d --expose 8080 -P jonlangemak/web_server_4_redirect user@docker1:~$ docker run --name web3 -d --net=container:web4 jonlangemak/web_server_3_8080
Because we published the port for the mapped container when we ran the primary container (web4
), we don't need to publish it when we run our mapped container (web3
). We should now be able to access each service directly through its published port:
user@docker1:~$ docker port web4 80/tcp -> 0.0.0.0:32771 8080/tcp -> 0.0.0.0:32770 user@docker1:~$ user@docker1:~$ curl localhost:32771 <body> <html> <h1><span style="color:#FF0000;font-size:72px;">Web Server #4 - Running on port 80</span> </h1> </body> </html> user@docker1:~$ curl localhost:32770 <body> <html> <h1><span style="color:#FF0000;font-size:72px;">Web Server #3 - Running on port 8080</span> </h1> </body> </html> user@docker1:~$
Care should be taken in mapped container mode to not attempt to expose or publish the same port on different containers. Since the mapped containers share the same network construct as the primary container, this would cause a port conflict.