Why won't my containers talk to each other?!
I spent a lot of time when I was first learning Docker wondering “Why won’t these containers talk to each other?!”
In this blog post, I want to share some things that I learned, which weren’t obvious to me at the time, but have since become pretty fundamental to my understanding of containers!
How containers talk to each other (it’s mostly networking)
There are a two main ways that containers can talk to each other:
-
Networking: (which I’ll focus on in this post) - where an application in a container might want to call an API somewhere else, or maybe expose its own service, e.g. think of a web server that needs to expose port 80, so it can receive HTTP requests.
-
Sharing files on disk: where an application does work by reading and writing files, e.g. a payroll application which writes payslips, which are read by an application which converts them into PDFs. For that, you might use a volume, which is basically a virtual disk that can be shared with other containers.
There are probably other ways that a container can communicate, but those are the main two that I can think of.
If you need two containers to communicate with each other, you’re probably going to use a virtual network.
How to get containers talking (the quick way)
When Docker starts up, it creates a default network called bridge
. Every container you start gets automatically added to this network and gets its own IP address. That’s it!
Here’s a quick example:
# Start an nginx container
docker run --rm --name mywebsite --detach docker.io/library/nginx
# Get the container's IP address
docker inspect mywebsite | grep IPAddress
"IPAddress": "172.17.0.2"
# Now any other container in the bridge network
# talk to nginx using that IP address
So any other application in this bridge network can now reach that container at 172.17.0.2.
But using IP addresses is clunky, because they can change. There is a much better way!
The better way: custom networks for your container groups
When I was first playing with Docker, I just used the default bridge network for everything. It worked fine… until I needed to run lots of containers on my dev machine. There were containers all over the place, and everything was mixed together, fighting over ports.
This is where user-defined networks are really useful. They’re basically like private networks, and you choose which container goes into each network.
These custom networks are mega-useful because they solve 3 problems:
-
isolating groups of containers from other groups
-
being able to use DNS names to talk to containers (instead of IP addresses) - e.g.
http://website
instead ofhttp://172.17.0.2
-
running multiple environments on the same machine - e.g. running your dev and test containers, both on the same machine, without fear that one will touch the other!
A real example: connecting a web app to its database
OK, enough of the theory. Here’s a real example. Here’s how you might create a typical setup, with a Node.js API and Postgres - something I work with almost daily:
Note that we use the --name
argument to set a fixed name for the container. When we do that, we often use --rm
so that Docker removes the container once it exits. Otherwise, the next time you run this command, Docker will say that the container already exists!
# First, create our network, which we'll add our containers into
docker network create myapp-net
# Start Postgres in a container
docker run --rm --name postgres \
--net myapp-net \
-d docker.io/library/postgres:17
# Start the Node.js API (with Express, or whatever you're using)
docker run --rm --name api \
--net myapp-net \
-e PGHOST="postgres" \
-e PGPORT="5432" \
-p 3000:3000 \
-d my-nodejs-api
I’m using -p 3000:3000
to expose my API’s port to the outside world. But I don’t expose Postgres’s port 5432 to the outside world, because I only want my app to talk to it. That makes this setup more secure.
TL;DR - what I wish I’d known
-
Don’t use the default bridge network for real projects. It’s fine for learning and experimenting, but you’ll soon quickly move on from its limitations.
-
Create a custom network for each app or set of related containers - just use
docker network create ____
-
Use
--name
to give your containers meaningful names - when combined with a custom network, this means it’s really easy for your containers to address each other with hostnames rather than IPs -
Keep different environments separate - e.g. one network for
dev
, one network fortest
, etc.
And some common gotchas, in case you’re wondering:
-
It’s easy to forget to add
--net
when starting a container. If you don’t add it, Docker will add your container to the bridge network which we want to avoid. -
If your containers talk to each other, but you can’t access your app from outside the container, then you might have forgotten to add
-p
to forward your main app’s port!
Want something quick to try out? Then copy the example above and replace with your own app/database setup. You can use this as a starting point for your own container experiments.
Happy containerising! 🐳