
Docker Compose networking lets containers find and contact the services they need. Most connection mistakes start with three questions left unanswered: where is the caller running, which network does it share with the destination, and which port is the destination listening on?
This guide uses a small web-and-client example to answer those questions. It then separates network membership, host access, and application readiness so you can diagnose a failed connection without exposing extra ports.
Compose normally creates a project network and connects its services to it. Containers on that network can resolve service names through Docker DNS. Use a service name and its container port for these connections, rather than copying a container's current IP address. The Compose networking guide explains this behavior.
Compose's project network is distinct from Docker Engine's built-in network named bridge. User-defined bridges provide name resolution between containers; the built-in bridge has different defaults. These bridges operate on one Docker daemon host, as described in the bridge driver documentation. A shared name in two Compose files does not create a network across two servers.
Create a separate directory and save the following as compose.yaml. Use a local Docker Engine with Linux containers, the Compose plugin, and an unused host port 8088. Keep this exercise separate from existing projects. The images use readable tags for the example; production deployments should record the exact image versions or digests they have tested.
services:
web:
image: nginx:stable-alpine
ports:
- "127.0.0.1:8088:80"
client:
image: alpine:3.22
command: ["sleep", "3600"]
The web service runs Nginx. The client service stays available for one hour so you can execute a request from inside its container. It has no published port because it does not need to receive requests from the host.
From that directory, validate the file, then start this disposable project:
docker compose -p net-demo config --quiet
docker compose -p net-demo up -d
We validated the configuration with Docker Compose v5.1.4. The resolved model places both services on net-demo_default and retains the loopback binding, host port 8088, and container port 80. The Docker engine was stopped in our test environment, so we did not pull images, start containers, or test live connections. The remaining runtime commands are instructions for your own test environment.
docker compose config checks and renders the Compose model. A successful result does not establish that an image can be pulled, a host port is free, or a service can answer requests. Keep that distinction when reviewing a deployment.
| Caller | Address for this example | Reason |
|---|---|---|
| The client container | http://web:80 | Uses the service name and the port inside the web container. |
| A browser on the Docker host | http://127.0.0.1:8088 | Uses the published host port. |
| Another machine | No remote endpoint is intended | The mapping explicitly binds to host loopback. |
With ordinary separate container network namespaces, localhost refers to the caller's own container. Asking the client to contact localhost:80 therefore targets the client, not the web service. Similarly, web:8088 confuses the host-side mapping with the server's listening port.
For a service running on the host instead, Docker Desktop provides host.docker.internal. Follow its host-connection instructions; do not assume that name exists automatically on every native Linux Engine installation. With a remote Docker context, the Docker host is also not necessarily the laptop running the CLI.
After the two containers are running, use these commands:
docker compose -p net-demo exec -T client wget -qO- http://web:80
docker compose -p net-demo port web 80
docker compose -p net-demo logs web
docker network inspect net-demo_default
The first command executes wget inside the client container; a successful request should return the Nginx page HTML. The port command should report the host mapping for container port 80. Logs and network inspection provide separate evidence about the server and the containers attached to the network. See the Compose exec reference for running commands inside a service.
If the request fails, keep the exact error and check one layer at a time:
A name-resolution error, connection refusal, timeout, and HTTP error response are different clues. An HTTP response means that a server answered; it does not mean the application returned a successful result. Record the failing URL and the test location before changing network configuration.
ports publishes a container port through the host. Without a host IP in the mapping, Docker normally publishes on all host addresses. This example explicitly uses 127.0.0.1 for local testing. Docker documents a localhost-publishing exposure issue in Engine versions older than 28.0.0, so use a supported, patched engine and verify the intended reachability. See port publishing and mapping.
expose does not create a host port mapping or replace an application listener. Nor is it an access-control list restricting every other port within a shared bridge network. The Compose service reference describes its syntax. Add network segmentation and application authentication when those are required.
For a real deployment, decide which entry point needs remote access and configure it deliberately. Do not publish a database port just to fix a service-name typo. Our Docker on a Compute VM guide covers the host setup and port-exposure decisions.
The following standalone configuration is a topology example. Its three Alpine containers only sleep; the names describe intended roles, not working proxy, application, or database software.
services:
proxy:
image: alpine:3.22
command: ["sleep", "3600"]
networks: [front]
app:
image: alpine:3.22
command: ["sleep", "3600"]
networks: [front, back]
store:
image: alpine:3.22
command: ["sleep", "3600"]
networks: [back]
networks:
front: {}
back:
internal: true
Our configuration check confirms that proxy joins front, store joins back, and app joins both. Only back is marked internal: true. That setting creates an externally isolated network, while a service also attached to a normal network can still have external connectivity. Review the Compose network attributes when adapting the file.
Draw the required communication paths before choosing memberships. A process attached to both sides deserves particular attention because it can communicate with each side. This pattern does not replace credentials, authorization, or a review of what that application forwards. Validate the intended allowed and denied paths in the deployment environment.
Use an external network when its lifecycle is managed outside the current Compose project. The network must already exist on the same Docker daemon. In this separate example, its exact name is team-demo-network:
services:
client:
image: alpine:3.22
command: ["sleep", "3600"]
networks: [shared]
networks:
shared:
external: true
name: team-demo-network
Our resolved-model check preserves that explicit name without a project prefix. Coordinate ownership, participating services, and naming before connecting another project. Treat membership as a deliberate increase in reachability. This file alone does not create the network or prove that another project is attached.
Being able to discover a container does not establish that its application is ready. Compose's short depends_on form establishes startup order; a dependency with condition: service_healthy can wait for a configured health check. Use the startup-order documentation and keep application retry behavior appropriate to later failures.
Likewise, avoid switching to network_mode: host as a general connectivity fix. It changes the isolation model and makes ordinary port mappings inapplicable. The host driver documentation specifies its platform support. Our configuration check also confirmed that declaring both network_mode and networks is rejected.
Once you finish the first example, run this from its directory to stop and remove that project's containers and default network:
docker compose -p net-demo down
The Compose down reference explains removal behavior; external networks are not removed. Do not apply cleanup commands to an unrelated project.
Keep the Compose file, tool versions, source and destination addresses, and observed request results together. Our development environment guide provides a broader setup checklist. Here, eight configuration checks passed, covering network membership, port resolution, internal and external network settings, and two invalid configurations. Live DNS, connectivity, isolation, and application readiness still require runtime testing.
Pick one AI, compute, or storage workload and see the difference for yourself. Spin it up in minutes, or let our team map your fastest path to production.