Docker Compose networking basics


 | August 21, 2020

Last time we discussed to talk with docker containers by publishing and exposing ports. Now let’s use Docker compose to see how multiple services might talk to one another.


From last time, we were using ryanlabouve/hello-world we can run a tiny web server and provide it a default message.

# => runs the docker image
docker run -p 3000:3000 \\
       -e MESSAGE=test \\

curl localhost:3000 # => test

Talking between containers

Docker compose allows us to boot up multiple services from one configuration file (usually docker-compose.yml).

Let’s setup a simple service with the hello-node image we used last time.

# docker-compose.yml
version: "3.7"
    image: 'ryanlabouve/hello-node:v2'
      - "3001:3000"
		  - MESSAGE="Hello form tweedle a"
    image: 'ryanlabouve/hello-node:v2'
      - "3002:3000"
		  - MESSAGE="Hello form tweedle b"

And now let’s boot them up and say hi.

docker-compose up
# tweedle-a_1  |
# tweedle-a_1  | > hello-node@1.0.0 prod /usr/src/app
# tweedle-a_1  | > node index.js
# tweedle-a_1  |
# tweedle-b_1  |
# tweedle-b_1  | > hello-node@1.0.0 prod /usr/src/app
# tweedle-b_1  | > node index.js
# tweedle-b_1  |
# tweedle-a_1  | Example app listening at <http://localhost:3000>
# tweedle-b_1  | Example app listening at <http://localhost:3000>

curl <http://localhost:3001>
# => tweedle a

curl <http://localhost:3002>
# => tweedle b

And very similar to docker ps, we have commands we can use to inspect docker compose:

docker-compose ps

Which would output

Communication with private / internal services

Let’s assume these two services need to talk to each other, ideally within the isolated confines of the Docker network.

Let’s start by shelling in to one of our running containers:

docker exec -it 918618a84cd3 /bin/bash # use `docker ps` to grab the id

So let’s recall, from outside this is exposed as localhost:3001. Internally though it’s localhost:3000. If you weren’t sure you could check out our previous post about how to discover this.

We can verify this via curl:

apt-get update
apt-get install curl

curl localhost:3001
# => unreachable

curl localhost:3000
# => Test message

Next we need to reach the other box. To understand how, we’ll exit the bash session inside the container and inspect the Docker network that the containers are attaching to.

Docker network

If we use docker network ls to investigate the docker network these services are using we’ll see that it’s a named bridge network.

a bridge network uses a software bridge which allows containers connected to the same bridge network to communicate

And looking deeper, we can use docker network inspect talking-with-docker-compose-containers_default

So based on the Docker docs we see that “each container for a service joins the default network and is both reachable by other containers on that network, and discoverable by them at a hostname identical to the container name”

This means is we shell into the boxes, we can access either other by their respective container name. e.g. curl tweedle-a:3001.

apt-get update
apt-get install curl

curl tweedle-a:3000
# => Hello from tweedle a

curl tweedle-b:3000
# => Hello from tweedle b

Bonus: Inspecting a docker network from the inside

This is not strictly necessary to know to solve our original problem of communicating between multiple Docker Compose containers, but it’s a fun exercise to use more vanilla linux tools to poke around.

Scanning the network with NMAP

Let’s start by using nmap to scan around the network.

apt-get install nmap
# grab the current boxes IP
ip addr show # =>

Now that nmap is installed and know the containers ip, we can knock around the network using. We’ll end our known ip with a /24. This is called “CIDR Notation” and is a way to specify which IP’s we want to scan. In this case it’s everything on [172.18.0.XXX](<http://172.18.0.XXX&gt;) where XXX is 0-255 (i.e. 2^24).

nmap -sP

We are scanning from tweedle-a and can see tweedle-b on our local network. If we hit it with a port scan we should see 3000 open.

nmap -v -A

Here’s some fun information I’ve extracted from that output:

# Discovered open port 3000/tcp on
# ...
# 3000/tcp open  http    Node.js (Express middleware)
# | http-methods:
# |_  Supported Methods: GET HEAD POST OPTIONS
#|_http-title: Site doesn't have a title (text/html; charset=utf-8).

More info about nmap here:

There’s so many other entertaining networking topics we could dive into now, but we’ll wrap the post here. Stay tuned for our next post on learning Docker!