Docker Course
Docker Compose Networking
Compose creates a network automatically — that's what makes service-name DNS work out of the box. But the moment your application grows beyond one Compose file, or you need services in different stacks to talk to each other, you need to understand how Compose networking really works.
This lesson covers the default network Compose creates, how to connect services across different Compose projects, how to use existing external networks, and the networking patterns that appear in every serious production deployment.
The Default Network Compose Creates
When you run docker compose up without defining any networks, Compose automatically creates a single bridge network named after the project. The project name defaults to the directory name — so if your project lives in a folder called order-api, the network is named order-api_default.
Every service in the Compose file is attached to this default network automatically. Every service can reach every other service by name. This is why a freshly written Compose file just works — Compose wires up DNS for free without any network configuration on your part.
# See which networks Compose created for your project
docker network ls | grep order-api
# Inspect the default network
docker network inspect order-api_default
NETWORK ID NAME DRIVER SCOPE
9e1f3a5b7c9d order-api_default bridge local
[
{
"Name": "order-api_default",
"Driver": "bridge",
"Containers": {
"a1b2...": { "Name": "order-api-db-1", "IPv4Address": "172.18.0.2/16" },
"b3c4...": { "Name": "order-api-redis-1", "IPv4Address": "172.18.0.3/16" },
"c5d6...": { "Name": "order-api-api-1", "IPv4Address": "172.18.0.4/16" }
}
}
]
What just happened?
Compose created order-api_default and attached all three containers to it. Each container got its own IP in the 172.18.0.0/16 subnet. The container names — order-api-db-1, order-api-redis-1, order-api-api-1 — are also their DNS hostnames on this network. Notice the prefix order-api — that's the project name, derived from the directory. If two developers have the same project in differently named folders, their networks won't conflict.
Connecting Two Compose Projects
The default network is isolated — containers in one Compose project cannot reach containers in another by default. This is usually the right behaviour. But there are real cases where you need cross-project communication — a shared database stack used by multiple application stacks, or a monitoring stack that needs to reach every other stack's containers.
The solution is an external network — a Docker network created outside of any Compose project that multiple projects can join.
The scenario: Your organisation runs a shared infrastructure stack — PostgreSQL and Redis — used by multiple application teams. Each team's Compose file connects to the shared databases via an external network rather than running their own copies.
# Step 1 — Create the shared network manually (done once, by the infra team)
docker network create shared-infra-net
# docker-compose.yml for the SHARED INFRASTRUCTURE stack (infra team)
services:
postgres:
image: postgres:15-alpine
environment:
POSTGRES_PASSWORD: ${DB_PASSWORD}
volumes:
- postgres-data:/var/lib/postgresql/data
networks:
- shared-infra-net # attached to the shared external network
redis:
image: redis:7-alpine
networks:
- shared-infra-net
networks:
shared-infra-net:
external: true # this network was created outside this Compose file
# Compose will NOT create or delete this network — it just uses it
volumes:
postgres-data:
# docker-compose.yml for the ORDER API stack (application team)
services:
api:
build: .
ports:
- "3000:3000"
environment:
DATABASE_URL: postgresql://postgres:${DB_PASSWORD}@postgres:5432/orders
REDIS_URL: redis://redis:6379
networks:
- default # api's own default network (for internal services)
- shared-infra-net # also on the shared network to reach postgres and redis
networks:
shared-infra-net:
external: true # same external network — joins it, doesn't own it
# After starting both stacks:
docker network inspect shared-infra-net
"Containers": {
"a1b2...": { "Name": "infra-postgres-1", "IPv4Address": "172.20.0.2/16" },
"b3c4...": { "Name": "infra-redis-1", "IPv4Address": "172.20.0.3/16" },
"c5d6...": { "Name": "order-api-api-1", "IPv4Address": "172.20.0.4/16" }
}
# All three containers — from two different Compose projects — on the same network
# order-api-api-1 can reach infra-postgres-1 by the hostname "postgres"
What just happened?
Three containers from two completely different Compose projects are sharing one network. The API container from the order-api project can now connect to PostgreSQL using the hostname postgres — even though PostgreSQL lives in a different Compose project. The external network acts as a shared corridor between isolated projects. The external: true key in both Compose files tells Compose "this network belongs to nobody — just join it." Neither Compose file creates or deletes it. The infra team manages the network lifecycle independently.
Cross-project networking via external network
infra project
(external network)
order-api project
Both projects join the same external network. Containers reach each other by service name. Neither project owns the network lifecycle.
Network Aliases in Compose
Just like the --network-alias flag in docker run, Compose services can have aliases — alternative hostnames on a specific network. This is useful when you need a service to be reachable under a different name on one network than another, or when migrating service names without breaking dependent services.
services:
order-api:
build: .
networks:
backend-net:
aliases:
- api # reachable as "api" on backend-net
- order-service # also reachable as "order-service" on backend-net
frontend-net:
aliases:
- api-public # different alias on the frontend network
payment-api:
build: ./payment
networks:
backend-net:
aliases:
- payment # reachable as "payment" on backend-net
networks:
backend-net:
driver: bridge
frontend-net:
driver: bridge
# From inside any container on backend-net — all three resolve to order-api: # curl http://order-api:3000/health → 200 OK # curl http://api:3000/health → 200 OK # curl http://order-service:3000/health → 200 OK # From inside any container on frontend-net: # curl http://api-public:3000/health → 200 OK # curl http://order-api:3000/health → 200 OK (container name always works too)
What just happened?
The order-api service now responds to three hostnames on backend-net — its service name, plus two aliases. This is valuable during migrations: when renaming a service from order-api to api, you can add the new alias first, migrate all dependent services to use the new name, then remove the old alias — zero downtime renaming. Aliases are network-specific — api-public only works on frontend-net, not on backend-net.
Controlling the Project Name
The project name controls all Compose resource naming — containers, networks, and volumes all get the project name as a prefix. By default it's the directory name, which can cause collisions if multiple team members clone the same repo into different directories, or if you run the same project in two environments on one machine.
# Set the project name explicitly — overrides the directory name
docker compose -p my-order-api up -d
# All resources named: my-order-api_default, my-order-api-api-1, etc.
# Or set it in a .env file (Compose reads this automatically)
# COMPOSE_PROJECT_NAME=my-order-api
# Or set the name in docker-compose.yml at the top level
# name: my-order-api ← top-level key in the Compose file
# Check the project name of a running stack
docker compose ls
NAME STATUS CONFIG FILES my-order-api running /home/dev/projects/order-api/docker-compose.yml infra running /home/dev/infra/docker-compose.yml
What just happened?
docker compose ls shows all running Compose projects on the machine — the project name, status, and the Compose file path. Setting a project name explicitly with -p ensures all resources are named consistently regardless of which directory the project was started from. This is essential in CI/CD environments where the same project might be cloned to differently named workspace directories on different build agents.
Network Naming Collisions
Two developers both clone the same repo into a folder named app. Both run docker compose up. Both create a network named app_default. The second one fails because the network name is taken. Always set an explicit project name — either with -p, the name key in the Compose file, or COMPOSE_PROJECT_NAME in the .env — on any project that multiple people or processes will run on the same machine.
Teacher's Note
External networks are the cleanest solution when you have shared infrastructure — one Compose file owns the resources, all others just join. Never duplicate a database stack across multiple Compose files if they're on the same machine.
Practice Questions
1. The default Compose network is named after the what — which by default comes from the directory name?
2. To connect a Compose service to a Docker network that was created outside of any Compose project, you declare the network with which key set to true?
3. The Compose network key that lets you give a service additional hostnames on a specific network is called what?
Quiz
1. Two separate Compose projects need their containers to communicate. The correct approach is:
2. Two developers each clone the same repo into a folder named app and both run docker compose up on the same machine. What happens?
3. To see all currently running Compose projects on a machine — their names, status, and config file paths — which command do you run?
Up Next · Lesson 27
Docker Compose Volumes
Networks sorted — now let's go deep on how Compose manages volumes, how to share volumes across services, and the patterns that keep your data safe across the full deployment lifecycle.