SSD Nodes Learn Hosting plans →
How to do am Matt ConnorBy Matt Connor · Updated 2026-08-07

Docker Compose networking: wetin you need know

Learn how Compose default bridge and service-name DNS work, when host mode make sense, how projects share networks, and why published ports fit bypass UFW.

Wetin Compose build before your app start

Docker Compose networking start with one rule: docker compose up dey create private network for the project, attach every service to am, and allow those services reach each other by service name. You no need write even one networks: line to get that. Most confusion around Compose networking dey come because people no know say default don already dey there.

Here na small file. Save am as compose.yaml inside directory wey dem call shop.

services:
  web:
    image: nginx:1.27
    ports:
      - "8080:80"
  db:
    image: postgres:16
    environment:
      POSTGRES_PASSWORD: example

Bring am up and check wetin Docker create:

docker compose up -d
docker network ls

The list now get network wey dem call shop_default. Compose name am <project>_default, and project name dey default to lowercase directory name. You fit override am with docker compose -p myproject up -d or with top-level name: myproject inside the file. The driver na bridge, wey be virtual switch inside the host. Each container get address for private subnet, and outbound traffic dey translate to the host address as e dey go out.

docker compose down delete that network again. Na why stale container from old project fit keep network open: Docker go refuse with error while removing network: network shop_default has active endpoints, and the fix na to stop or remove the container wey still attach to am.

If Compose new to you, Compose file layout and lifecycle commands dey worth reading first, because everything below assume say you fit start and stop project.

DNS by service name na the part beginners dey miss

For any user-defined network, Docker dey run one embedded DNS server wey every container fit see for 127.0.0.11. E dey resolve service names to the current container addresses. So web fit reach the database for hostname db, on port 5432, without any configuration.

docker compose exec web getent hosts db

That command go print line like 172.18.0.2 db. If e no print anything, the two services no dey for the same network.

The mistake almost everybody dey make at least once na to use localhost for application config. Inside container, localhost na that container itself, no be the host and no be the other service. Postgres clients dey report am clearly:

could not connect to server: Connection refused
	Is the server running on host "localhost" (127.0.0.1) and accepting
	TCP connections on port 5432?

The connection string suppose be postgresql://postgres:example@db:5432/postgres. The host part na the service name.

Two details fit save you time later. Names resolve to wetin dey run now, so docker compose up -d --scale web=3 fit give one name with three addresses, and client wey cache DNS forever go pin itself to dead container. And the legacy bridge network wey plain docker run dey use without --network no get name resolution at all. Na why advice about container links from 2016 no match wetin you dey see.

You no need ports: to connect two services

ports: dey publish container port for host. E dey handle traffic wey come from outside Docker. E no get anything to do with service-to-service traffic, because dem already fit communicate across the full port range for the project network.

So the ports: - "5432:5432" wey plenty people dey add to their database service no help, and e dey cause real problem: e expose Postgres for the public interface of the server. Delete am. If you want make your laptop reach am for migration, bind am to loopback with "127.0.0.1:5432:5432" and reach am through an SSH tunnel. The difference between a listening socket, a published port, and a firewall rule dey explained for how ports and listening services dey work for Linux.

expose: na documentation only under Compose. E no open anything, because nothing close between containers wey dey for the same network.

When network_mode host dey right, and wetin e cost

Host mode remove the container own network namespace and allow the process use the host interfaces directly.

services:
  probe:
    image: alpine:3.20
    network_mode: host
    command: sleep infinity

Real reasons dey why person fit want this. Process wey need see broadcast or multicast traffic for local network, like device discovery for media server or home automation hub, no fit see am from behind bridge, because bridge no forward that traffic go the container. Monitoring agent wey dey read host interface counters need the host interfaces. You also skip the address translation hop, and this fit matter when packet rate high.

The costs dey specific.

ports: no longer work. Docker warn say published ports dey discarded when you use host network mode, and the container go bind any port wey its process bind. Two host mode containers wey both want port 8080 go collide, and the second one go die with bind: address already in use.

Service name resolution no longer work for both directions. The container no dey on the project network, so e no fit resolve db, and the other services no fit resolve am too. E fit reach dem only through ports published on the host, usually for 127.0.0.1.

Isolation don go. Process wey bind 0.0.0.0 inside host mode container dey listen on every interface for your server, including the public one, exactly like package wey you install with apt. One benefit dey: this traffic follow the normal input path, so UFW rules dey apply to am. This one no dey true for published ports.

Host mode na Linux Docker Engine feature. Docker Desktop support am only from version 4.34 upward and only after you enable am. E get extra limits: containers no fit bind host IP addresses, and only TCP and UDP dey handled. If half your team dey use Linux servers and the other half dey use Docker Desktop, expect the same file to behave differently.

Use host mode when you need the host interfaces. No use am to fix connection problem, because e usually replace one problem with another wey harder to solve.

Connect two Compose projects with an external network

Network wey one project create no dey visible to another project. Na why reverse proxy for proxy/compose.yaml no fit see app for app/compose.yaml, even when dem dey the same server. The fix na to use network wey no be property of either project.

Create am once, by hand:

docker network create edge

Then declare am as external for each project. The proxy side:

services:
  proxy:
    image: traefik:v3.1
    ports:
      - "80:80"
      - "443:443"
    volumes:
      - /var/run/docker.sock:/var/run/docker.sock:ro
    networks:
      - edge

networks:
  edge:
    name: edge
    external: true

The application side:

services:
  app:
    image: nginx:1.27
    networks:
      - edge
      - internal
  db:
    image: postgres:16
    environment:
      POSTGRES_PASSWORD: example
    networks:
      - internal

networks:
  edge:
    name: edge
    external: true
  internal:

external: true tell Compose make e attach to network wey already dey exist instead of creating new one, and make e leave am for docker compose down. The separate name: key important pass as e first look: without am, Compose go look for network wey name na exactly edge. With am, you fit use one name for your file and another name for the host.

If the network no exist, Compose no go start and e go report say the network was declared as external but e no fit find am. Create the network first.

Notice wetin the application file dey do with internal. The database dey only on that project-local network, so proxy no fit reach am and na only app fit reach am. If you add internal: true under a network, e go go further and remove the route wey e get to the outside world completely. This na good default for database, but know one cost before you set am: container wey dey on internal network no fit download anything, so entrypoint wey dey run apt-get update or pip install when startup happen go hang, then fail with timeout.

For complete worked setup with routing rules and certificates, see running plenty apps behind one Traefik instance.

Published ports dey bypass UFW

Na this part of Compose networking fit turn into security incident. You publish one port, check say UFW dey active and dey deny everything except SSH, but service still dey reachable from internet.

sudo ufw status
curl http://203.0.113.10:8080

UFW say the port dey blocked. The curl still dey return the page. Nothing spoil. Docker dey write im own address translation and forwarding rules directly into iptables. Traffic wey dey go published container port dey forward go the container instead of deliver to the host. So e no dey pass through the chain wey UFW dey manage for traffic wey target the local host. Docker rules dey match before UFW own rules too.

The short fix na to publish only for where you need am:

    ports:
      - "127.0.0.1:8080:80"

This one bind the host side to loopback. So the port dey reachable from the server itself and through SSH tunnel, but nowhere else. Put the public entry point behind reverse proxy wey intentionally publish 80 and 443. The full explanation, including the DOCKER-USER chain for cases wey you must filter published port, dey for why Docker publishes straight past UFW and how to fix it.

How to debug am with four commands

Start by asking which network each container really dey on:

docker network inspect shop_default

The Containers block dey list every container wey attach, plus e address. If service no dey for that list, e dey for another network, or e dey use host mode, or e no dey run.

Test name resolution from a temporary container wey attach to the same network. This way, you no need any tooling inside your own images:

docker run --rm --network shop_default busybox nslookup db
docker run --rm --network shop_default busybox nc -zv db 5432

If nslookup fail, e point to name resolution or network membership problem. If nslookup succeed while nc fail, e mean say the service dey run but e no dey listen for that port, or e dey listen for 127.0.0.1 inside e own container instead of 0.0.0.0. This one common with development servers. The fix dey for the application's bind address, not for Docker.

Another failure fit look like Docker bug. If containers fit talk to each other but dem no fit reach machine for your office or VPN network, Docker subnet probably dey overlap with that network. Docker dey allocate from 172.17.0.0/16 upward by default. Move the pool inside /etc/docker/daemon.json:

{
  "default-address-pools": [
    { "base": "10.200.0.0/16", "size": 24 }
  ]
}

Then run sudo systemctl restart docker and recreate the affected networks, because existing network go keep the subnet wey e use when dem create am.

FAQ

Why containers no fit reach each other with service name?

Dem no dey for the same network. Compose dey put every service for <project>_default automatically, but once you add networks: list to service, that list become the complete set of networks for am, and default network no longer dey included automatically. Run docker network inspect <network> and check say both containers dey show for Containers block. Also check say no service dey use network_mode: host, because container wey dey use host mode no dey for any Docker network and e no fit resolve service names.

I need publish ports make one service reach another?

No. For Compose network, every port for every container dey reachable by other containers wey dey for that network. ports: dey only to expose container to traffic wey come from outside Docker, while expose: na documentation. Publishing database port na common and costly habit, because e dey put database for your server public interface.

Wetin be the difference between bridge and host networking?

Bridge give container e own network namespace and address for virtual switch. E also provide automatic name resolution between containers and translate outbound traffic. Host give container direct access to host network stack: no separate address, no service-name resolution, no port publishing, and no isolation from other listeners for the host. Bridge na the default and correct option unless the process need the host interfaces.

How I fit connect containers from 2 different Compose files?

Create shared network with docker network create edge. Then declare am for both files with external: true and attach the services wey need communicate. Compose no go create or delete the network. If you skip the create step, Compose refuse to start and report say the network dey declared as external but e no dey found.

Why my container dey reachable from internet when UFW block the port?

Because Docker dey handle published port with forwarding rules wey e add to iptables. Those rules dey match before UFW rules, and forwarded traffic no dey pass through the chain wey UFW dey filter. Bind the host side to loopback with "127.0.0.1:8080:80" and put anything public behind reverse proxy for ports 80 and 443.