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

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 di part wey beginners dey miss

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

docker compose exec web getent hosts db

E go print line like 172.18.0.2 db. If e print nothing, the two services no dey on 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, 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 dey resolve to wetin dey run now, so docker compose up -d --scale web=3 go give one name with three addresses. Client wey dey cache DNS forever go pin itself to dead container. And legacy bridge network wey plain docker run dey use without --network no fit resolve container names at all. Containers for there fit find each other only by IP address, and dem dey look up outside hostnames through copy of host's /etc/resolv.conf. 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 correct, and wetin e cost

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

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

Real reasons dey to use am. 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 dey forward that traffic to container. Monitoring agent wey dey read host interface counters need host interfaces. You also skip address translation hop, and that fit matter when packet rate high.

The costs dey specific.

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

Name resolution by service name don disappear for both directions. Container no dey on project network, so e no fit resolve db, and other services no fit resolve am too. E still fit reach dem through ports published on host, usually for 127.0.0.1. For Linux, e fit also reach bridge container directly through that container IP address on any listening port, because host get open access to containers for its bridge networks, and host mode container dey share host network stack. Those addresses dey change when containers recreate, so port wey publish on loopback na the stable route.

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

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

Use host mode when you need host interfaces. No use am to fix connection problem, because most times e go replace one problem with harder one.

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 use:

docker network inspect shop_default

The Containers block list every attached container with its address. If service no dey for that list, e dey on different network, or e dey host mode, or e no dey run.

Test name resolution from throwaway 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. If nslookup succeed while nc fail, the name and network dey fine, and na the connection itself get problem. If nc report Connection refused, the usual cause na say service no dey listen on that port, or e dey listen on 127.0.0.1 inside its own container instead of 0.0.0.0. This one common with development servers. The fix dey for application's bind address, no be for Docker. If nc hang and later time out, look for something wey dey drop traffic on the way, like firewall rule, or container wey pause or no dey answer.

One more failure fit look like Docker bug. If containers fit talk to each other but no fit reach machine for your office or VPN network, Docker subnet probably 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 dey 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.

Dis article help you?