Docker Compose Networks
Networks are one of the places where Docker Compose starts to feel much more useful than manually managing containers. You already know how Docker networks work; now Compose gives you a way to describe the application’s network structure alongside its services.
Why Use Networks With Compose?
Imagine an application with two services:
app
databaseThe application needs to communicate with the database.
With the Docker commands you’ve already learned, you would create a user-defined bridge network and connect both containers to it.
Conceptually:
application network
┌──────────────────┐
│ │
│ app ─────── database
│ │
└──────────────────┘With Compose, the network can become part of the application’s configuration.
Instead of separately creating the network and then connecting containers to it, you describe the relationship in compose.yaml.
This is another example of the central Compose idea:
Describe the application and its relationships in configuration instead of manually assembling them with separate commands.
The Default Compose Network
Compose Creates a Network for the Application
Start with a small example.
Create a directory:
mkdir compose-networks
cd compose-networksCreate compose.yaml:
services:
first:
image: alpine
command: ["sleep", "infinity"]
second:
image: alpine
command: ["sleep", "infinity"]There is no networks section yet.
Run:
docker compose up -dNote
Even though you did not explicitly define a network, Compose creates a default network for the application.
The important idea is:
compose.yaml
│
├── first service
└── second service
│
▼
Compose networkBoth services are connected to that Compose-managed network.
This means Compose gives the application a network environment even when you don’t explicitly configure one.
The default network is associated with the Compose project. You don’t need to memorize the exact generated name. What matters is the relationship:
Compose project
│
├── first
├── second
│
└── default networkThe services in the same Compose application can communicate through this network.
This is especially useful because you don’t have to manually create a network before starting the application.
Service Names Become Useful on the Network
Let’s make the example demonstrate communication between services.
We’ll use two containers:
clientserver
Create this compose.yaml:
services:
server:
image: alpine
command: ["sh", "-c", "while true; do echo 'hello from server'; sleep 5; done"]
client:
image: alpine
command: ["sleep", "infinity"]Start the application:
docker compose up -dBoth services are now part of the same Compose application’s default network.
A very useful property of this network is that services can use their service names to refer to one another.
So from the application’s point of view:
client ───────► server
↑
service nameThe name server identifies the other service within the Compose network.
This is much more useful than trying to discover and hard-code a container’s IP address.
Note
Container IP addresses are implementation details that can change. In a Compose application, you normally communicate with another service using its service name rather than depending on its IP address.
Why Service Names Matter
Suppose your application has:
services:
app:
...
database:
...The application can conceptually connect to:
databaserather than something like:
172.18.0.4The relationship becomes:
Compose network
┌─────────────────────────────┐
│ │
│ app ───────────────► database
│ │
└─────────────────────────────┘This makes the configuration much easier to understand.
You are saying:
“The app talks to the database service.”
rather than:
“The app talks to whatever container currently happens to have this IP address.”
Explicitly Defining Networks
Explicitly Defining a Network
The default network is convenient, but Compose also allows you to explicitly define networks.
Let’s modify the example.
Your compose.yaml file:
services:
server:
image: alpine
command: ["sh", "-c", "while true; do echo 'hello from server'; sleep 5; done"]
networks:
- app-network
client:
image: alpine
command: ["sleep", "infinity"]
networks:
- app-network
networks:
app-network:Now the structure is explicit:
services
├── server ─────┐
│ │
└── client ─────┤
│
▼
app-networkThe networks section at the bottom declares the network:
networks:
app-network:Each service then says which network it belongs to:
services:
...
networks:
- app-networkThis is useful when you want the network structure to be visible and controlled as part of the application’s configuration — which is often if not always.
One Network, Multiple Services
A single network can connect several services.
For example:
services:
app:
image: alpine
command: ["sleep", "infinity"]
networks:
- app-network
database:
image: alpine
command: ["sleep", "infinity"]
networks:
- app-network
worker:
image: alpine
command: ["sleep", "infinity"]
networks:
- app-network
networks:
app-network:The resulting structure is:
app-network
┌───────┼───────┐
│ │ │
app database workerAll three services are attached to the same network.
This is the Compose version of the user-defined bridge network concept you already learned.
Multiple Networks
Compose also becomes useful when an application needs more than one network.
For example, imagine:
frontend
backend
databaseYou might want:
frontend ───── frontend-network ───── backend
│
│
backend-network
│
▼
databaseYou can describe this explicitly:
services:
frontend:
image: alpine
command: ["sleep", "infinity"]
networks:
- frontend-network
backend:
image: alpine
command: ["sleep", "infinity"]
networks:
- frontend-network
- backend-network
database:
image: alpine
command: ["sleep", "infinity"]
networks:
- backend-network
networks:
frontend-network: {}
backend-network: {}Now the relationships are:
frontend
│
│ frontend-network
▼
backend
│
│ backend-network
▼
databaseThis demonstrates a powerful principle:
Network membership controls which services share a network.
The Compose file becomes a description of the application’s communication structure.
Why This Is Better Than Manually Connecting Containers
Without Compose, you might have to think through several individual operations:
Create network A
Create network B
Create container A
Connect container A to network A
Create container B
Connect container B to networks A and B
Create container C
Connect container C to network BWith Compose, the relationships are visible together:
services
├── frontend → frontend-network
├── backend → frontend-network + backend-network
└── database → backend-network
networks
├── frontend-network
└── backend-networkThe Compose file is now acting almost like a diagram of the application’s communication architecture.
That is one of the reasons Compose configuration becomes valuable as applications grow.
Networks as Project Resources
Compose Networks Are Project Resources
Remember the lifecycle lesson.
Compose manages the resources described by the application.
Networks are part of that picture too.
When you run:
docker compose up -dCompose creates the networks required by the configuration if they don’t already exist.
When you run:
docker compose downCompose removes the networks it created for the application, subject to the network’s configuration and lifecycle rules.
You don’t need a separate manual network-creation workflow for a network that belongs to the Compose application.
A Network Can Belong to Several Services
A common beginner question is:
“Does each service need its own network?”
No.
A network is a shared communication space.
For example:
app-network
┌───────────┼───────────┐
│ │ │
app database workerServices that need to communicate can share the same network.
The important question isn’t:
“Does every container need a network?”
The better question is:
“Which services need to communicate with each other?”
Then design the network membership around that requirement.
Network Design Is About Communication Boundaries
This is the mental model to carry forward:
Network A
┌─────────────────┐
│ app worker│
└─────────────────┘
Network B
┌─────────────────┐
│ worker database│
└─────────────────┘Here:
app <-> worker
worker <-> databaseBut app and database are not directly connected to the same network.
The network structure itself communicates something about the application’s architecture.
You aren’t just creating networks because Docker requires them.
You’re defining which components share a communication boundary.
When Should You Explicitly Define Networks?
For a small Compose application, the automatically created default network is often enough.
For example:
services:
app:
image: alpine
database:
image: alpineYou don’t need to write a networks section just to make these services share a network.
Explicit networks become useful when you want meaningful network names in the configuration, have multiple networks, different services need different network memberships, or you want the application’s communication structure to be obvious from the Compose file.
Don’t add network configuration just because you can.
Tip
Start with the default Compose network. Define explicit networks when the application’s communication requirements actually need them.
External Networks And Resource Flags
What About External Networks?
If you need a network that should be attached to your service container but whose lifecycle is managed outside the Compose file (i.e. you create / rm the network outside the Compose file), then you can use that network with the external: true key value.
services:
mybox:
image: busybox
networks:
- other
networks:
other: {}Important
If the other network doesn’t exist, Compose reports an error even with the --all-resources flag.
What Is the --all-resources Flag?
If you define a top-level resource but don’t use it in any services, Compose won’t create that resource by default. You have to tell it explicitly with the --all-resources flag.
For example, you have this compose.yaml:
services:
web:
image: nginx
mybox:
image: busybox
networks:
mynetwork: {}Here you defined mynetwork but never referenced or used it in any services (web or mybox). If you run:
docker compose upCompose won’t create your mynetwork resource. You should use the --all-resources flag:
docker compose up --all-resourcesPutting It All Together
A Complete Small Example
Create compose.yaml:
services:
frontend:
image: alpine
command: ["sleep", "infinity"]
networks:
- frontend-network
backend:
image: alpine
command: ["sleep", "infinity"]
networks:
- frontend-network
- backend-network
database:
image: alpine
command: ["sleep", "infinity"]
networks:
- backend-network
networks:
frontend-network:
backend-network:The architecture is:
frontend-network
┌────────────────────┐
│ │
frontend ◄──────────────► backend
│
│
backend-network
│
▼
databaseThe Compose file expresses the architecture directly.
The frontend and backend share one network.
The backend and database share another.
The backend acts as the bridge between those two application areas.
You can start it with:
docker compose up -dAnd when you’re finished:
docker compose downMermaid View of the Same Architecture
The network structure can also be represented visually:
flowchart LR
frontend["Frontend"]
backend["Backend"]
database["Database"]
frontend_net(("frontend-network"))
backend_net(("backend-network"))
frontend --- frontend_net
backend --- frontend_net
backend --- backend_net
database --- backend_net
The important part isn’t the diagram syntax.
The important part is what the diagram communicates:
frontendshares a network withbackendbackendshares another network withdatabasefrontendanddatabasedo not share a network directly
Cleanup
Stop and remove the Compose application:
docker compose downThen return to the parent directory:
cd ..If you no longer need the example:
rm -rf compose-networksWarning
rm -rf permanently removes the specified directory and its contents. Make sure you are in the correct parent directory before running it.
What You Should Remember?
Compose doesn’t replace Docker networking.
It gives you a way to describe Docker networking as part of the application.
The progression is:
Docker networking
│
▼
You learned how containers
communicate through networks
│
▼
Compose lets you describe
those relationships
│
▼
compose.yaml
│
├── services
│
└── networksFor a simple application, the default Compose network is often enough.
When you need more control, define explicit networks and assign services to them.
The most important mental model is:
Services are the components of your application. Networks define which of those components can communicate through a shared network.
Next, you’ll apply the same Compose approach to another Docker resource you’ve already learned: volumes. This will let you describe not only how your services communicate, but also where their persistent data lives.