Networking with Compose
When you run docker compose up, Compose creates a default network for your stack and connects all services to it. This works for simple setups, but for anything with multiple tiers (web, API, database), you should define networks explicitly to control which services can communicate.
Default Behavior
If you do not declare any networks, Compose creates one named <project>_default and puts every service on it:
# Every service can reach every other service
services:
web:
image: nginx:alpine
api:
image: my-api:1.0.0
db:
image: postgres:16
This means the database is reachable from the web server, which is probably not what you want.
Explicit Networks (Recommended)
Define separate networks and assign services to only the ones they need:
services:
web:
image: nginx:alpine
ports:
- "80:80"
- "443:443"
networks: [front, back]
api:
image: my-api:1.0.0
networks: [back]
db:
image: postgres:16
networks: [back]
networks:
front: {}
back: {}
flowchart LR
subgraph front["front network"]
Web["web<br/>(ports 80, 443)"]
end
subgraph back["back network"]
API["api"]
DB["db"]
end
Web API
API DB
Internet -->|"Published ports"| Web
style front fill:#e3f2fd,stroke:#1565c0
style back fill:#e8f5e9,stroke:#2e7d32
In this topology:
webbridges both networks -- it can reachapibut also accepts external trafficapianddbare onbackonly -- they are invisible to the outside worlddbis never published -- only reachable fromapivia Docker DNS
Service Discovery in Compose
Within a shared network, services resolve each other by their Compose service name:
services:
api:
image: my-api:1.0.0
environment:
DB_HOST: db # Resolves to the db container
DB_PORT: "5432"
networks: [back]
db:
image: postgres:16
networks: [back]
networks:
back: {}
Do not use container IPs in your configuration. Use service names -- they survive container recreation.
Internal-Only Services
Services that should never be reachable from outside the Docker host:
services:
db:
image: postgres:16
# No "ports:" key -- not published to host
networks: [back]
No ports: = no host binding = only reachable from other containers on the same network.
External Networks
To connect services to a network that was created outside of Compose (e.g., shared between multiple Compose projects):
services:
api:
image: my-api:1.0.0
networks: [shared]
networks:
shared:
external: true # Must already exist: docker network create shared
Use this when multiple Compose stacks need to communicate.
Debugging Compose Networking
Validate the Merged Config
# See the final resolved configuration (catches override issues)
docker compose config
Check Running Services and Networks
docker compose ps
docker network ls
docker network inspect myproject_back
Test DNS from Inside a Container
docker exec -it myproject-api-1 sh
nslookup db
nc -vz db 5432
Troubleshooting Workflow
| Problem | Check |
|---|---|
| Service A cannot reach Service B | Are they on the same network? docker network inspect |
| DNS name not found | Is the service running? docker compose ps |
| Connection refused | Is the target listening on the expected port? |
| Works locally, not in Compose | Are you using localhost instead of the service name? |
Key Takeaways
- Define networks explicitly in any stack with more than two services. Do not rely on the default network for production.
- Only services that share a network can discover each other via DNS.
- Do not publish internal services (databases, caches). Keep them on internal networks only.
- Publish only the ingress service (reverse proxy or web frontend) to the host.
- Use
docker compose configto validate your final network topology before deploying.
What's Next
- Continue to Network Troubleshooting Toolkit for a structured approach to diagnosing connectivity failures.