Docker

Docker Compose Bridge Networking

Docker Compose is an easy way for deploying multi-container applications. It automates a lot of the booking keeping, networking and resource management of applications in a single neat docker-compose.yml file. You can get the app up by running docker-compose up and turn it back down using docker-compose down.

A lot gets added to our Docker environment, that gets overlooked and then deleted with the last command. One of the most important object is a Bridge network. This is what we shall be focusing on. More precisely, bridge networking.

Docker Bridge Network

Docker has many networking related drivers. Two of the most important ones are Bridge networking driver and Overlay one. The latter is used for docker swarm mode, where containers running over different nodes can have still be a part of single abstract subnet. Bridge networking, however, is the one that interests us here.

To create a new Docker Network called my-network and inspect it, run:

$ docker network create -d bridge my-network
$ docker inspect my-network

You will see, among other things, a subnet mask and a default gateway.


"Config": [
{
"Subnet": "172.18.0.0/16",
"Gateway": "172.18.0.1"
}

Any container that gets connected to this network will get an IP in the range from 172.18.0.2 to  172.18.255.254. Let’s try creating a couple of containers on this network:

$ docker run -dit --name container1 --network my-network ubuntu:latest
$ docker run -dit --name container2 --network my-network ubuntu:latest

If you now run, inspect my-network you will notice that individual containers with their proper name and corresponding IP addresses show up in the containers field of the JSON output.

$ docker inspect my-network
...
"Containers": {
"8ce5cd67e6aed180b5d0b6b0fcd597175d6154c9208daa9de304aec94757e99b": {
"Name": "container1",
"EndpointID": "93d020d22172d6c98a0b88e78a7a01f6d1a3d44b983e7454fba7c1f1be5fae9d",
"MacAddress": "02:42:ac:12:00:02",
"IPv4Address": "172.18.0.2/16",
"IPv6Address": ""
},
"af1434df6f86d2df96aca1d7348dd6c815a4989ec07fb0f3cfea95d4a38b4f74": {
"Name": "container2",
"EndpointID": "3a5f57639c71685a10584fd392c20abc5ae693684860bef486404d26b332395a",
"MacAddress": "02:42:ac:12:00:03",
"IPv4Address": "172.18.0.3/16",
"IPv6Address": ""
}

If you create another network my-network2, it will have a different subnet mask like 172.19.0.0/16 and containers on it will be isolated from containers on other networks. So, ideally, you want one network per application, so every app is secure and isolated from one another.

How Compose Creates a Network

Docker Compose understands the idea behind running services for one application on one network. When you deploy an app using Docker Compose file, even when there’s no mention of specific networking parameters, Docker Compose will create a new bridge network and deploy the container over that network.

If the docker-compose.yml is in directory my-app, the directory’s name will be used to name the network as well as the containers mounted on top of it. For example, if I create a directory:

$ mkdir my-app
$ cd my-app
$ vim docker-compose.yml

And add the following contents to the docker-compose.yml file:

version: '3'
services:
my-nginx:
image: nginx:latest

Notice, how we didn’t expose any ports. Let’s deploy this app:

$ docker-compose up -d

This creates a new network called my-app_default using bridge network driver that we discussed before.You can list all the networks on your personal setup using docker network ls and then pick the network interface that matches your directory’s name. Once you have the name of the network you can docker inspect to see all the containers that are a part of that network along with their individual IP addresses and subnet mask.

If we create another container, using directly the CLI (this is not recommended for real-world use cases) on this network, we can actually talk to our my-nginx service.

$ docker run -dit --name container4 --network my-app_default ubuntu:latest
$ docker exec -it container4 bash
root@a32acdf15a97:/# curl http://my-app_my-nginx_1

This will print an html file with familiar snippets like “Welcome to Nginx” visible in it. The nginx web server is reachable from within the network without us having to publish any ports! More importantly, you don’t even have to reach it using its private IP, you can simply call it by its hostname (that’s container name as shown in docker ps).

When running a database and connecting it to the frontend, you won’t have to publish the Database port at all. Instead, you can reach the DB from the web server just by calling its predictable hostname. Even when docker compose is run elsewhere where and the IP and the subnet may now differ, the containers will still be able to talk to one another.

Of course, to publish a port to the outside world we would write something like the following:

version: '3'
services:
my-nginx:
image: nginx:latest
port:
- “8080:80”

Now people can access the web server from port 8080 at the IP of your Docker Host. This can be, for example, the public IP of your VPS or just localhost if you are running Docker on your desktop. Yet again, I emphasis, you don’t have to expose any ports for your database container, because the web server can talk to it directly and thus this reduces the risk of databases exposed to the Internet.

When you bring your application down, using:

$ docker-compose down

This custom bridge network along with all the ephemeral containers that were created and attached on top of it, using the docker-compose.yml file, will get deleted. Leaving your Docker environment in a clean state.

Defining your own network

Compose allows you to define your own network definition. This would include options for subnet mask, IPv6 addresses, among other things. The way it is done is that we have a top level networks just like services or version are top level keys. This key has no indendentation. Under the networks key, we can now define various attributes of the network, for now we will keep it simple and just mention that it ought to use bridge driver.

version: ‘3’
networks:
my-network:
driver: bridge

Now each container can connect to multiple networks, so under services section we mention this custom network’s name. The networks key here expects a list of networks.

version: '3'
services:
my-nginx:
image: nginx:latest
networks:
- my-network
- some-other-network # This is another network that you might have created.

Lastly, the order in which the network is defined and then used inside a service definition is relevant. So the whole yml file will look something like this:

version: '3'
services:
my-nginx:
image: nginx:latest
networks:
- my-network
networks:
my-network:
driver: bridge

Further Information

While writing your own network definitions you may want to refer to the official documentation. For a quick glance at top level networks key visit this link and for the service level networks key here’s the reference.

You can also try and specify subnets in the top level networks definition so that the services can have a predetermined range of IP addresses.

About the author

Ranvir Singh

I am a tech and science writer with quite a diverse range of interests. A strong believer of the Unix philosophy. Few of the things I am passionate about include system administration, computer hardware and physics.