Docker Compose Networks: Service Discovery and Isolation (2026)
Docker Compose puts every service in a project on one network and lets them reach each other by service name. Most of the time you never write a networks: block at all. You need one when you want to isolate a database, share a network between two Compose projects, or attach to a network something else created.
The default network
Compose creates one network per project automatically, named <project>_default:
services:
web:
image: nginx
db:
image: postgres
Both services join <project>_default. From inside web, the database is reachable at the hostname db — the service name. No networks: key, no IP addresses, no links.
Use the container port, not the published one. Inside the network you connect to
db:5432, the port the app listens on. Theports:mapping only matters for reaching the container from your host.
Service name DNS
Compose runs a DNS resolver on the network, so every service name resolves to that service's container:
services:
api:
image: myapi
environment:
DATABASE_URL: postgres://user:pass@db:5432/app
CACHE_URL: redis://cache:6379
db:
image: postgres
cache:
image: redis
If you scale a service to several containers, the name resolves to all of their IPs and Docker returns them in rotation — basic round-robin without a load balancer.
container_name overrides the container's name but not the DNS name. Aliases are what you want for a second name:
services:
db:
image: postgres
networks:
default:
aliases:
- database
- postgres.local
Custom networks
Declare networks at the top level and assign services to them. Services only reach services on a network they share:
services:
proxy:
image: nginx
networks: [frontend]
api:
image: myapi
networks: [frontend, backend]
db:
image: postgres
networks: [backend]
networks:
frontend:
backend:
proxy reaches api. api reaches db. proxy cannot reach db — it is not on backend. This is the standard way to keep a database off the network your public-facing service sits on.
Note that as soon as a service lists any network, it no longer joins the default one.
Internal networks — no outbound access
Add internal: true and containers on that network get no route out to the internet:
networks:
backend:
internal: true
Useful for a database that should never make outbound connections. Be aware it also blocks package installs and certificate fetches from those containers.
External networks
To join a network created outside this Compose file — by another project, or by docker network create — mark it external:
services:
app:
image: myapp
networks: [shared]
networks:
shared:
name: shared_net
external: true
Compose will not create or delete it, and it fails fast if shared_net does not exist. This is how you let two separate Compose projects talk to each other — a common setup when one project runs a reverse proxy for several others.
Why one container cannot reach another
Almost every "connection refused" between Compose services is one of these:
- They are on different networks. Once a service declares
networks:, it leaves the default one. Check withdocker network inspect <project>_default. - You used
localhost. Inside a container,localhostis that container. Use the service name. - You used the published host port.
ports: - "5433:5432"means the host reaches it on5433, but other containers still use5432. - The app binds to
127.0.0.1. A process listening only on loopback is unreachable from the network even when everything else is right. Bind to0.0.0.0. - The other service is not up yet.
depends_onwaits for the container to start, not for the app inside to be ready. Use a healthcheck withcondition: service_healthy, or retry in the client.
To reach a service running on the host rather than in another container, see extra_hosts and host-gateway. For network_mode: host, see Docker host networking.
Let the platform handle networking
On a single machine this is manageable. Across several servers it stops being a Compose problem — you are into overlay networks, service discovery, and firewall rules between hosts.
Hostim.dev runs your services on a managed network: apps reach their databases by name, nothing else is exposed, and HTTPS and routing are handled for you.
👉 Deploy an app and its database — networking handledFrequently asked questions
Do I need to define networks in Docker Compose?
Usually not. Compose creates one network per project and puts every service on it, so services already reach each other by service name. Define networks only when you want to isolate some services from others, block outbound access with internal: true, or join a network created outside the Compose file.
How do containers talk to each other in Docker Compose?
By service name. Compose runs a DNS resolver on the project network, so a service named db resolves to that container's IP from any other service on the same network. Connect to the port the app listens on inside the container, for example db:5432 — not the port published to the host.
What is the default network in Docker Compose?
A bridge network named <project>_default, created automatically when you run docker compose up. All services join it unless they declare their own networks key. Once a service lists any network, it no longer joins the default one, which is the most common reason two services suddenly cannot reach each other.
How do I connect two Docker Compose projects?
Create a shared network with docker network create shared_net, then in each project declare it with external: true and attach the services that need it. Compose will not create or delete an external network, and it fails immediately if the network does not exist. This is the usual setup when one project runs a reverse proxy in front of several others.
Why can't my container connect to another container?
The common causes are: the two services are on different networks because one declared a networks key and left the default; the client used localhost, which inside a container means that container itself; the client used the published host port instead of the container port; the target app binds to 127.0.0.1 instead of 0.0.0.0; or the target container has started but the app inside is not ready yet, which depends_on alone does not solve.
What does internal: true do in a Compose network?
It removes the route to the outside world for containers on that network. They can still reach each other, but cannot make outbound connections to the internet. It suits a database that should never call out, but it also blocks package installs and certificate fetches from those containers.