Facilitating communication between docker containers using networks, links and aliases
Networking and inter-container communications become an important consideration when running multiple docker containers together as part of an single deployment. There are a number of options for facilitating inter-container communications when running docker containers that fall into two broad categories:
- Networks - associating a container with either a default or user-defined network
- Discovery - facilitating name-resolution so containers discovery each other
In this article we will discuss network and discover options for inter-container communication for containers running on a single host.
The docker engine creates three networks by default:
The none network
As the name suggests, the none network doesn't add any network to a container. What this means is that inside the container, there is only a loopback interface. This prevents any network connectivity either into or out of the container. This is appropriate for containers that do not require network connectivity. Interaction with the container is limited to stdin and stdout.
To run a container using the none network, use the command
> docker run --net=none [OPTIONS] IMAGE [COMMAND] [ARGS]
The bridge network
The built-in bridge network attaches to the docker0 interface. This interface is created when docker is installed. When running a container, this network is used by default. Bridge networks are isolated on a single host. This means that an IP in the network can be accessed from the host that the bridge network is defined on, but not from outside the host.
To run a container using the default bridge network, you do not need to provide any value to the --net argument. So:
> docker run [OPTIONS] IMAGE [COMMAND] [ARGS]
is equivalent to
> docker run --net=bridge [OPTIONS] IMAGE [COMMAND] [ARGS]
You can create you own bridge network on a host using the network create command:
> docker network create -d bridge --subnet 172.25.0.0/16 my-bridge
This command creates a bridge network with a subnet of 172.25.0.0 and a gateway of 172.25.0.1
Now when you want to use this network with a container, you would use the command:
> docker run -d --net=my-bridge [OPTIONS] IMAGE [COMMAND] [ARGS]
This command runs the container and places it in the my-bridge network and will be assigned an IP address from the networks subnet (e.g. 172.25.0.2, 172.25.0.3, and so on).
The host network
When you specify --net=host, the container runs on the host's network without network isolation. In practice this means that ports made available through EXPOSE commands are bound to the host network. Care must be taken to avoid port conflicts when using this option. For example, consider the following set of command
> docker run -d --name=web --net=host nginx:latest #works, listens on host's port 80 and 443 > docker run -d --name=web2 --net=host nginx:latest #error, since ports 80 and 443 are already bound
Name Resolution between containers
Now that we can control the network behaviour of our containers, how do we get containers to talk to each other?
Let's take the fairly simple multi-container application with the names:
- web - a static webserver
- api - a dynamic webserver
- db - a database
- reverse - a reverse-proxy with SSL termination and reachable from the internet on ports 80 and 443
In this example the reverse container and the api container need to know how to address the web and db containers respectively.
If all four containers are running on the default bridge network on the same host, they will have IP addresses like:
172.17.0.2 172.17.0.3 172.17.0.4 172.17.0.5
These IP addresses can change whenever you remove and re-run one of the containers. As a result, hard-coding these IPs in the code inside your containers is not very practical.
Docker provides two techniques to facilitate inter-container communication:
- network scoped alias
Linking using the --link option creates a link between containers. The link is set up by the container that is consuming the link. So, if you want to be able to reach web from reverse, you would use the commands:
> docker run --name=web [OPTIONS] IMAGE [COMMAND] [ARGS] > docker run --name=reverse --link=web [OPTIONS] IMAGE [COMMAND] [ARGS]
What this achieves is that from within the reverse container, there is a host entry for the name web that resolves the bridge network IP address of the web containers.
There are some limitations to this technique:
- the container being linked to needs to be running before the container doing the linking.
- if multiple containers are linked to each other, you need to keep track of the links to coordinate start-up order.
The other technique is to use the --net-alias option. Rather than the consuming container settng up the association, --net-alias provides a way for a container to be discovered by name by any other container within the scope of a user-defined network.
Using the same example as before, you can make the web container addressable with the alias web-1 by the reverse container (and any other container on the same network) with the commands:
> docker run --name=web --net=my-bridge --net-alias web-1 [OPTIONS] IMAGE [COMMAND] [ARGS] > docker run --name=reverse --net=my-bridge [OPTIONS] IMAGE [COMMAND] [ARGS]
Notice that this time, the naming is initiated by the web container instead of the reverse container.
Now the reverse container can use the address web-1 to refer to the web container. This is an improvement over using the --link option since it removes the start-up order dependency. It can also be much easier to manage names and their dependencies. You can even use multiple aliases for a container on multiple networks!
No matter how simple your deployment you should take care to understand the network options available for your containers. Using linking or network scoped aliases facilitates inter-container communication.
You should consider using network scoped aliases rather than linking for easier management of your containers.