Multiple Docker Compose's in a Single Network
Quick Introduction
Even after using Docker/Containers for years, it's not entirely obvious in how to join many Docker Compose projects using a single Docker network. For a while I was abusing the default bridge network - but given the limitations (e.g. container name resolution is disabled) it was starting to get annoying.
So this is a quick walk-though in creating a network manually, and then using that network within multiple docker-compose projects.
To the CLI!
Create the Network
First we can create a network. In my case, I'm calling it internal
:
The subnet was picked randomly by myself, really any can be used, just make sure there's no subnet overlapping on your network. I'm specifying a ip-range
because I want to host a DNS container in this network (and DNS clients typically need a explicit IP address) - 172.24.0.128/25
is just a subset of the 172.24.0.0/24
. The driver is likely always going to be brdige
, but if you need to vlan or bridge with the host's network, you might use a different driver.
What's nice about manually created networks, everything you would expect to work, just works e.g. container name resolution. There's no default limitations like the default bridge
network.
Configure Docker Compose
Now specifying this manually created network is rather straight forward, there's two sections involved that would be set on each Docker Compose projects you have.
If you use the VS Code Docker extension - you have access to both syntax checking, auto-complete, and inline-documentation.
First, on the top-level of the docker-compose.yaml
file, specify the manually created network (again internal
in this example). To tell docker-compose to suppress the default managing of the network, external: true
is used.
And second, on the container's themselves, specify the manually created network, and this can be done in two ways:
Using YAML list syntax:
Or using YAML object syntax (need if you are going to further configure the network for this container).
Note that here I'm specifying a static IP address for the container. If no address is specified, a dynamic IP address will be assigned based on your IP range when creating the network (still a static IP address, just dynamically assigned by Docker, e.g. DHCP isn't being used here).
There's a lot of options you have access to when using YAML object syntax, for example, aliases
:
Aliases allows you to provide alternative and resolvable names within the network specified. So for example here, containers within the
internal
network will be able to resolveanother-container-name
and get the IP address of this container.
The Default Network
So you might notice that when you bring up this Docker Compose, that the default network is still being created by Docker Compose (in this example, example_default
):
Implicitly, the Docker Compose will create a network called default
as well as set networks
to default
This is neat because this allows you to expose individual containers to the "external" network.
An Example
So at the end of the day, if I was setting up a DNS server within my docker network, my Docker Compose would look like:
I'm forcing running as
root
here just so I can bind to port53
, as remember, there's no Docker port mapping, like we would see if we exposed ports to our Docker host. It feels a bit nasty, but that's how port allocations work on Linux.
And say I want to have my Warrior container using this DNS server, I have another docker-compose.yaml
file with this:
I'm also configuring my warrior container to use the DNS container above (with the static IP address of 172.42.0.2
).
Warrior is a daemon you can run locally, it helps the Archive.org project get around rate limiting issues with their WayBackMachine. While I'm always happy to donate some of my bandwidth to archiving projects - warrior likes to each RAM, depending on what the current project is - so I explicitly limit the RAM for system stability.
Bonus: Traefik
Traefik (Pronounced "Traffic") is a common ingress container for Docker - it can automatically find your containers and act as a reverse proxy for them. This is really cool when you combine Traefik's native Let's Encrypt support and any containers you want to host on the same HTTPS port.
A common problem when using traefik
occurs when you have multiple Docker Composes, normally each compose project creates an isolated network - so different container projects can't really talk to each other - and this messes up Traefik (since Traefik is also running in a separate network).
The equally common solution has been to use the bridge network mode:
But as I've mentioned before, this has limitations, and is kind of a hack. If we configure Traefik to use a manually created network, we get the bests of both worlds.
I use the following traefik
compose on many of my external servers.
Using the manually created Docker network does clean up the standard traefik
deployment.
When joining Traefik to the same network as all your containers, you might want to enable
providers.docker.exposedByDefault = false
to prevent containers from being accidentally exposed.