← Blog
Three cream ceramic blocks connected by green cables with a coral collar on a pale lavender background.
Published on
2026-10-30

Docker Compose networking: service names, ports, and isolation

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.

How the default Compose network works

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.

A small Docker Compose networking example

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.

Choose the address from the caller's location

CallerAddress for this exampleReason
The client containerhttp://web:80Uses the service name and the port inside the web container.
A browser on the Docker hosthttp://127.0.0.1:8088Uses the published host port.
Another machineNo remote endpoint is intendedThe 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.

Verify the connection and the port mapping

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:

  1. Confirm the intended Docker context and project, then check that both containers are running.
  2. Inspect network membership and the service name used by the caller.
  3. Check the destination process, its listening address, and its port.
  4. Test the actual application protocol from the caller's location.
  5. Review host access separately from container-to-container access.

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.

Use ports and expose for their intended purposes

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.

Separate front and back networks

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.

Share an existing network between projects

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.

Distinguish readiness from networking

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.

Finish the exercise and record what worked

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.

Your next workload belongs on Hivenet.

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.