Set up Networks in Compose
Master configuring Docker Compose networks — custom bridges, aliases, external networks, and multi-service isolation with hands-on exercises.
Focus: set up networks in compose
Have you ever started multiple Docker services with docker-compose up only to find they can't talk to each other, or worse, conflicting ports accidentally exposed to the whole world? Without explicit network configuration, Compose creates a default bridge that works for simple cases but quickly breaks down as soon as you need service isolation, custom DNS resolution, or connections to containers outside your Compose file. That's why setting up networks in Compose isn't optional — it's the control plane for all inter-service communication in your distributed applications.
The problem this lesson solves
Out of the box, Compose auto-creates a single default network with no service isolation. Every container on that network can reach every other container by service name — which is convenient, but also dangerous. Consider a microservices app where a public-facing frontend and a private database share one network. A vulnerability in the frontend gives an attacker direct access to your DB. There's no network firewall between them.
Other problems compound quickly:
- Two services expose the same container port (e.g., 5432). Without separate bridge networks, DNS resolution gets ambiguous.
- You need a reverse proxy (like Nginx) that must reach multiple backends, but those backends should never talk to each other.
- Your Compose app must connect to a legacy database container running outside the Compose file (on a separate host or a pre-existing Docker network like docker-compose up creates).
Custom network definitions in docker-compose.yml solve all these by letting you define isolated bridge networks, assign container aliases, and even attach to external "docker compose networks" that already exist.
Core concept / mental model
Think of a Docker Compose network as a software-defined VLAN. Each network is a separate virtual switch. Containers plugged into the same switch can see each other; containers on different switches cannot — unless you explicitly connect them.
The mental model has three layers:
1. Service: a containerized app defined under services: — it can list networks: it wants to join.
2. Network definition: declared under networks: in your Compose file — specifies the driver (usually bridge), IPAM configuration, and whether it's external.
3. Container aliases: DNS names (other than the service name) that other containers can use to reach your container on that network.
Key terms
- bridge: The default Docker network driver; works on a single host.
- external: A network not created by this Compose file (e.g.,
docker network create my-prod-net). - aliases: Extra hostnames resolved to this container on the given network.
- ipam: IP address management — you can specify subnet ranges.
This model lets you design networks like you design AWS VPCs or Kubernetes network policies, but at the Compose level with zero additional tooling.
How it works step by step
Setting up networks in Compose follows a declarative pattern. Here's the logical flow:
1. Declare the networks in the top-level networks: block
networks:
frontend-net:
driver: bridge
backend-net:
driver: bridge
This creates two isolated bridges. No container is connected yet — you need step 2.
2. Attach services to networks
Each service lists the networks it wants to join:
services:
web:
build: ./web
networks:
- frontend-net
api:
build: ./api
networks:
- frontend-net
- backend-net
db:
image: postgres:16-alpine
networks:
- backend-net
Now:
- web can only talk to api (both on frontend-net)
- api can reach db (both on backend-net)
- web cannot reach db — network isolation achieved
3. (Optional) Configure advanced options
- Aliases: give a container extra DNS names on a specific network
- External: reference a network that already exists
- IPAM: set custom subnet, gateway, IP ranges
4. Verify with docker compose exec
docker compose exec web ping api
docker compose exec web ping db # should fail (timeout)
docker compose exec api ping db # should succeed
Hands-on walkthrough
Create a project folder with three services: a reverse proxy (Nginx), a frontend (React static site), and an API (Python Flask). Let's isolate the frontend from the API but keep the proxy able to reach both.
Step 1: Write docker-compose.yml
version: '3.8'
services:
nginx:
image: nginx:alpine
ports:
- "80:80"
networks:
- frontend-net
- backend-net
# Nginx will proxy /api -> api:5000, / -> frontend:3000
frontend:
build: ./frontend
networks:
- frontend-net
api:
build: ./api
networks:
- backend-net
environment:
- DB_HOST=db # this container does NOT have db reachable — we'll fix in a moment
db:
image: postgres:16-alpine
environment:
POSTGRES_PASSWORD: secret
networks:
- backend-net
networks:
frontend-net:
driver: bridge
backend-net:
driver: bridge
Wait — the API needs to talk to the database but both are on backend-net. That's correct! The API and DB share one private bridge, while Nginx straddles both networks. The frontend only sees Nginx, never the DB or API directly.
Step 2: Add network aliases for clarity
Sometimes a different DNS name is helpful. Add aliases to the API:
api:
build: ./api
networks:
backend-net:
aliases:
- my-api
- internal-api
Now Nginx can proxy requests to my-api:5000 or internal-api:5000 instead of just api:5000.
Step 3: Connect to an external network
Assume you already have a production network called prod-net:
docker network create prod-net
Reference it in Compose:
networks:
prod-net:
external: true
Then add any service you want to that external network:
metrics-collector:
image: prom/prometheus
networks:
- backend-net
- prod-net # collects metrics from other containers on prod-net
Step 4: Test the isolation
# From the nginx container, both services are reachable
docker compose exec nginx ping frontend
docker compose exec nginx ping api
# From the frontend container, only nginx is reachable
docker compose exec frontend ping nginx
docker compose exec frontend ping api # Should fail: frontend is only on frontend-net
Expected output for the failed ping: ping: api: Name or service not known
Compare options / when to choose what
| Scenario | Recommended Network Setup | Reason |
|---|---|---|
| Simple two-service app (e.g., web + DB) | Default network (no custom nets) | Minimal config; both services need to talk |
| Multi-layer app (frontend + API + DB) | Three networks: frontend, backend, db |
Isolate frontend from DB; API acts as bridge |
| Connected to legacy containers | Use external: true network |
No need to refactor existing infrastructure |
| Same service needs different DNS names per network | Use aliases |
Enables clean reverse proxy configuration |
| IP address must be predictable | Set ipam config with fixed IPs |
Audit / legacy IP whitelisting |
| Multiple Compose files sharing a network | Define one network as external and reference it |
Avoid port conflicts and enable cross-Compose communication |
Pro tip: When a service needs to access another service but shouldn't be exposed to the internet, create an internal network (no
ports:mapping) and attach both services to it. This is the Docker equivalent of a private subnet.
Troubleshooting & edge cases
Problem: Containers can't reach each other by service name
Cause: They are on different networks. Each container only resolves DNS names for services attached to the same network.
Fix: Check which networks each service is attached to with docker compose ps or docker inspect <container>. Add the missing network to the service definition.
Problem: "Network external-net declared as external, but could not be found"
Cause: You declared networks: prod-net: external: true but that network doesn't exist yet.
Fix: Create the network manually before docker compose up:
docker network create prod-net
Problem: Service A can reach Service B even though they share no network
Cause: You may have linked them in a way that bypasses networks: (e.g., using links: in older Compose v2).
Fix: Remove links: (deprecated) and ensure only networks: controls connectivity. Use depends_on if you need boot ordering.
Edge case: IP address conflicts with external network
Cause: Two Compose files both try to assign the same subnet to their internal networks, conflicting with a manually created network.
Fix: Explicitly configure ipam with unique subnets:
networks:
my-net:
driver: bridge
ipam:
config:
- subnet: 10.5.0.0/16
Edge case: Env variables with hostnames don't work after attach
Cause: Environment variables are evaluated at container start, before networks are fully resolved. Using hostnames in env vars is fragile. Fix: Use DNS-based discovery (service names resolved by Docker's built-in DNS) rather than fixed environment variables containing IPs or hostnames.
What you learned & what's next
You now know how to:
- Declare custom bridge networks in docker-compose.yml
- Attach multiple services to isolated networks
- Use aliases for flexible DNS resolution
- Connect to external, pre-existing networks
- Verify network isolation with docker compose exec
This is the core networking skill for any Compose-based project. Without it, your applications are either fully exposed or unable to communicate when you need them to.
Up next: Lesson 34 — Service discovery & DNS in Compose. You'll learn how Docker's built-in DNS resolves service names, how to override DNS resolution with custom entries, and how service discovery differs across bridge, host, and overlay networks. You'll apply the network isolation patterns from this lesson to a real multi-service app with DNS-based load balancing.
Practice recap
Create a docker-compose.yml with three services: a static site (nginx), a Node.js API, and a Redis cache. Attach the static site and API to a 'frontend' network, then attach the API and Redis to a 'cache' network. Verify the static site cannot reach Redis by name, and the API has access to both. Then, add an alias 'my-cache' for Redis on the cache network and test it with docker compose exec api ping my-cache.
Common mistakes
- Forgetting to add a service to any network — containers on the default network can't be reached by name from custom network services unless you explicitly attach them.
- Using
links:instead ofnetworks:to connect services. Links are deprecated and break when you scale services or use aliases. - Assuming containers on one default bridge network can reach containers on a different custom bridge network — they cannot without explicit inter-network connection or external network setup.
- Declaring an
externalnetwork without creating it first —docker compose upwill fail with 'network not found' error unless you pre-create it withdocker network create.
Variations
- Use
docker network createand thenexternal: truein Compose to connect to already running containers outside your Compose project. - Use
networks:withipamconfiguration to reserve a specific subnet, enabling static IP assignment for legacy systems that require hardcoded IPs. - Define network aliases at the service level to give containers multiple hostnames on the same network, useful for reverse proxy configurations.
Real-world use cases
- A three-tier web app: Nginx on 'public' network, Flask API on both 'public' and 'private' networks, PostgreSQL on 'private' only — isolates database from public exposure.
- A monitoring stack: Prometheus and Grafana on a shared 'monitoring' network, but separate from app networks — keeps metrics collection invisible to end users.
- A multi-Compose environment: legacy app using its own Compose file on
external: truenetwork, new microservices in another Compose file attaching to the same external network for data exchange.
Key takeaways
- Custom networks are the primary mechanism for service isolation in Docker Compose — use separate bridge networks for frontends, backends, and databases.
- Each service can attach to multiple networks; this is how you create a 'bridged' architecture where one service (e.g., API) straddles two isolated zones.
- Network aliases give containers extra DNS names on a per-network basis — essential for reverse proxy configurations or scaling scenarios.
- External networks let Compose services connect to containers managed outside the Compose file, enabling hybrid deployments and microservice federation.
- Always verify network isolation with
docker compose execand ping tests — don't assume unlisted networks block connections by default. - Declare
networks:at the service level, not thecontainer_name:level — Compose handles the container-network attachment automatically.
Keep learning
Related tutorials, quizzes, and articles for this topic.
Discussion
Questions, corrections, and tips help everyone reading this page.
0 comments
Add a comment
No comments yet — start the thread.