Skip to main content

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.

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:

  • web bridges both networks -- it can reach api but also accepts external traffic
  • api and db are on back only -- they are invisible to the outside world
  • db is never published -- only reachable from api via 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

ProblemCheck
Service A cannot reach Service BAre they on the same network? docker network inspect
DNS name not foundIs the service running? docker compose ps
Connection refusedIs the target listening on the expected port?
Works locally, not in ComposeAre 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 config to validate your final network topology before deploying.

What's Next