Docker Compose Networking Explained: Services, DNS, and Ports
Docker Compose networking is the layer that lets the API, database, worker, cache, and other services in a Compose application find one another and exchange traffic. Compose creates useful defaults, but understanding those defaults matters when an application has more than one network, needs access from the host, or fails with an apparently mysterious connection error. This guide explains Docker Compose networking from the service-name lookup through the host port and into practical troubleshooting.
What Is Docker Compose Networking?
Docker Compose networking is the combination of Docker networks and Compose configuration that gives services private connectivity and discoverable names. When you run a Compose project, Compose normally creates a project-scoped default network and connects each declared service to it. A service can then reach another service by its service name, such as db, rather than by a container IP that may change on the next restart.
The Docker documentation for networking in Compose describes this behavior and the commands for inspecting it. Compose uses Docker Engine networking underneath; it does not create a separate packet-forwarding technology. The Compose file describes which containers should share which Docker networks, while the engine creates interfaces, addresses, routes, and embedded DNS entries.
The Compose Specification defines networks as the top-level objects that services can join. A network is therefore both a connectivity boundary and a service-discovery scope. A service attached to two networks can communicate with members of both, while a service attached to only one cannot normally reach the other network.
Why Does Compose Need Networking?
A multi-container application has dependencies with different exposure requirements. A reverse proxy may need a host-facing port, an API may need to reach a database, and a database should usually not be reachable directly from the host or from unrelated services. Running every container with unrestricted host networking makes those relationships difficult to express and easy to expose.
Compose networking addresses four practical problems:
- Stable discovery: Clients use
db:5432orapi:8080even when container addresses change. - Private east-west traffic: Services can communicate on a Docker network without publishing every port to the host.
- Controlled ingress: Only selected services need a
portsmapping for traffic arriving from the host or LAN. - Segmentation: Separate frontend, backend, and data networks can limit which services can open connections to one another.
This model is especially valuable in local development, where containers are routinely recreated. It also makes a Compose file a readable description of application topology instead of a list of unrelated docker run commands. For the broader Docker lifecycle, see our guide to Docker containers, images, volumes, and networking.
How Compose Networking Works
The project default network
For a file named compose.yaml in a project directory, a command such as docker compose up -d typically creates a network with a project-derived name, such as myapp_default. Services without an explicit networks section join this network automatically.
services:
api:
image: example-api:1.0
db:
image: postgres:16
The api container can connect to PostgreSQL at db:5432. The name db is the service name, not a hostname manually placed in each container’s /etc/hosts file. Docker’s embedded DNS answers for names on the shared user-defined network and returns the current service endpoint. The address can change when the service is recreated without requiring application configuration to change.
The container port is used for service-to-service traffic. A ports entry is not required for api to reach db; publishing is only needed when a client outside the relevant Docker network must enter through the host.
Network interfaces and packet path
Each attached container receives a network interface in its network namespace. Docker connects that interface to a user-defined bridge on the host. A request from api to db follows a path similar to:
api process
-> api container interface
-> Compose project network
-> embedded DNS resolves "db"
-> db container interface
-> database process on port 5432
The request does not leave the host merely because the application uses a hostname. The name is resolved inside the Docker network, and the traffic stays on the local virtual network unless routing or another network attachment takes it elsewhere. Docker’s networking overview explains the bridge, namespace, routing, and port-publishing layers in more detail.
Host-to-container traffic
To reach a service from the host, publish a port:
services:
web:
image: nginx:1.27
ports:
- "127.0.0.1:8080:80"
The mapping means HOST_ADDRESS:HOST_PORT:CONTAINER_PORT. A request to the host’s loopback address on port 8080 enters the published-port path and is forwarded to port 80 in the web container. The application inside the container still listens on port 80. EXPOSE in a Dockerfile documents a port but does not publish it.
Binding to 127.0.0.1 keeps this example local to the host. Binding to 0.0.0.0 or omitting the host address can make the port reachable through the host’s network interfaces, subject to firewall and platform behavior. Publish only the ports that need external access.
Compose Networking Components and Variants
Service names, aliases, and container names
The service key is the normal discovery name:
services:
api:
image: example-api:1.0
worker:
image: example-worker:1.0
environment:
API_URL: http://api:8080
Use service names in application configuration. Avoid using container_name as a scaling or discovery mechanism: it creates a fixed name, can cause collisions, and makes multiple replicas awkward. When a network needs an additional stable name, use an alias:
services:
api:
image: example-api:1.0
networks:
backend:
aliases:
- users-api
networks:
backend:
Aliases are scoped to the network. A name that resolves on backend is not automatically available to a container connected only to frontend. Hostname syntax and related naming rules are also constrained by Internet host requirements described in RFC 1123.
Explicit user-defined networks
Declare named networks when the topology should be obvious or when services need different connectivity:
services:
proxy:
image: nginx:1.27
ports:
- "127.0.0.1:8080:80"
networks:
- frontend
- backend
api:
image: example-api:1.0
networks:
- backend
db:
image: postgres:16
networks:
- backend
networks:
frontend:
backend:
Here, proxy can reach api, but db is not attached to frontend and has no direct connection to the proxy’s public-facing network. The network boundary is not a substitute for database authentication or application authorization, but it reduces accidental reachability.
Internal and external networks
An internal network is intended for services that should not have normal external connectivity through that network:
networks:
backend:
internal: true
Use an external network when Compose should join a network created outside the current project:
networks:
shared-proxy:
external: true
name: shared-proxy
The external network must already exist. This is useful when several Compose projects share a reverse proxy, but it also creates an intentional cross-project trust boundary. Document which services are attached and avoid treating a shared network as a universal application bus.
IP addresses and static network settings
Prefer service discovery over hard-coded container addresses. Static IP configuration can be useful for a special integration, but it introduces subnet planning and lifecycle coupling. If static addresses are required, define an IPAM subnet and ensure it does not overlap with office, VPN, cloud, or host routes. Most web applications need service names, health checks, and timeouts instead.
Real-World Use Cases
API, database, and cache
An API, PostgreSQL database, and Redis cache can share a private backend network. The API uses the database and cache service names with their container ports; neither data service needs a published host port. A developer may publish the API on the host loopback address for browser testing while keeping database traffic inside Docker.
Reverse proxy in front of multiple services
A proxy can join a frontend network shared with the host-facing entry point and a backend network shared with internal APIs. This allows one public entry point without publishing every application port. The proxy still needs explicit routing configuration and the applications still need authentication.
Worker and queue
A worker can share a private network with a message broker while remaining disconnected from the frontend network. The worker should use the broker’s service name and handle startup races with retries or a health-aware dependency strategy. Network reachability only proves that a connection can be attempted; it does not prove that the broker is ready to accept work.
Practical Guide: Configure and Troubleshoot Compose Networks
Create a minimal compose.yaml:
services:
web:
image: nginx:1.27
ports:
- "127.0.0.1:8080:80"
networks:
- edge
- app
db:
image: postgres:16
environment:
POSTGRES_PASSWORD: local-only-password
networks:
- app
networks:
edge:
app:
internal: true
Start and inspect it:
docker compose config
docker compose up -d
docker compose ps
docker compose network ls
docker network inspect <project>_app
docker compose port web 80
The exact project prefix depends on the directory or configured project name. Use docker compose config to catch YAML and interpolation errors before creating containers. Use docker compose exec web to run a diagnostic command inside a running service:
docker compose exec web getent hosts db
docker compose exec web sh -c 'nc -vz db 5432'
docker compose logs web db
If the image does not include getent or nc, use a temporary diagnostic image attached to the same network:
docker run --rm --network <project>_app nicolaka/netshoot \
sh -c 'getent hosts db; nc -vz db 5432'
Troubleshoot in layers:
- Configuration: Run
docker compose configand confirm the service is attached to the expected network. - Lifecycle: Check
docker compose psand service logs. A stopped process cannot accept connections. - Discovery: Resolve the service name from the caller’s container. Do not test
localhost; inside a container, it means that same container. - Listening address: Confirm the destination process listens on its container interface, not only on
127.0.0.1. - Port meaning: Use the container port for service-to-service traffic and the published host port from the host.
- Segmentation: Check whether both services share a network and whether an
internalor external-network choice is intentional. - Host path: For published-port failures, inspect bind addresses, host port conflicts, firewalls, and Docker Desktop platform behavior.
Remove the project when the experiment is complete:
docker compose down
Use docker compose down -v only when deleting named volumes is intentional; networking and storage have different lifecycles. For a deeper comparison of bridge, host, and overlay behavior, see Docker networking drivers. Our container networking guide covers the same concepts across Docker and Kubernetes.
Common Misconceptions
“The host port is the port other containers should use”
Usually not. Containers on the same Compose network should use the destination service name and its container port, such as the api service on port 8080. The published host port is for clients entering through the host.
“depends_on means the dependency is ready”
Basic startup ordering does not guarantee that a database has finished initialization or is accepting connections. Add a health check where appropriate and make the client retry transient startup failures.
“A service name is a permanent IP address”
No. The name is a stable lookup interface; the returned address can change when a container is recreated. Applications should resolve the name and handle connection failures rather than cache an address indefinitely.
“A shared network is a security boundary by itself”
It is a useful reachability boundary, not complete authorization. Protect services with credentials, least-privilege application permissions, host firewall rules, and encrypted transport where needed.
Related Articles
- Docker Compose for local development explains the broader multi-container workflow.
- Docker networking drivers compares bridge, host, and overlay network behavior.
- Container networking explained covers namespaces, CNI, Services, policy, and multi-host paths.
- Docker containers for beginners connects networking to images, volumes, and the container lifecycle.

