dockernetworkingdevopscontainers

Docker networking explained: bridge, host, ports and container communication

How containers communicate: default bridge, custom networks with internal DNS, host networking, publishing ports right and debugging connectivity.

August 27, 2026·8 min read

"Connection refused" between two containers is the quintessential Docker error, almost always born from not understanding that each container lives on its own network with its own localhost. This article organizes the complete networking model — what exists, when to use it, and how to debug it when it fails.

The localhost trap

First mental collision: inside a container, localhost points at THE CONTAINER ITSELF. Your app in container A calling localhost:5432 looks for a database inside itself — doesn't find it. The DB runs in container B with its own isolated network stack.

The whole Docker networking model answers that reality. Solutions, most to least recommended:

Default bridge (and why to avoid it)

Configuring nothing, containers run on bridge — the docker0 network (172.17.0.0/16). There, containers see each other by internal IP but not by name: no automatic DNS. Communicating demands dynamic IPs changing on every recreation... fragile to the point of absurdity.

It's the right network for one-off containers (docker run). For multi-container stacks, no.

Custom networks: free built-in DNS

Creating a user-defined network enables Docker's embedded DNS server (127.0.0.11): every container resolves others by container name:

docker network create myapp-net
docker run -d --name db --network myapp-net postgres:16-alpine
docker run -d --name app --network myapp-net -e DATABASE_URL=postgres://app:x@db:5432/app myapp

That db hostname in the connection string is internal DNS resolving the container name. With Docker Compose this is automatic: all services in a compose share the project's network and discover each other by service name — exactly the app + PostgreSQL stack pattern.

Extra aliases for flexibility:

services:
  db:
    networks:
      myapp-net:
        aliases: [postgres, database]

Publishing vs exposing ports

Two distinct concepts worth not mixing:

EXPOSE (in Dockerfile): pure documentation — declares which port the process uses. Opens nothing.

-p / ports (at run time): publishes a HOST port redirected into the container. -p 8080:80 = your machine at 8080 → container 80.

Practical rules preventing scares:

  1. Publish only what must be reachable from outside the host (usually just the reverse proxy or frontend).
  2. Databases and internal services: unpublished. Reach them via the internal network.
  3. Need local emergency access to an internal service? Publish bound to loopback: -p 127.0.0.1:5432:5432. Never 0.0.0.0 for anything private.
  4. Two containers publishing the same host port = startup conflict.

Host mode: when the container IS the machine

--network host removes isolation: process runs directly on the host's network stack. No port mappings (listened ports are real), maximum network performance, and maximum risk too: nothing separates container from system.

Sensible cases: high-performance proxies, monitoring tools that must see all traffic. Outside Linux it doesn't behave the same (Docker Desktop simulates it); use as justified exception, never for convenience.

Cross-project networks

By default networks are per-project in Compose (project_default). A global Traefik proxying several projects? Shared external network:

networks:
  proxy-net:
    external: true

services:
  web:
    networks: [proxy-net]
docker network create proxy-net

All projects joining proxy-net become reachable by Traefik without exposing individual ports — the standard self-hosting pattern with Nginx/Traefik as single gateway.

Debugging when it fails

Minimal kit, in suspicion order:

# 1. Are they on the SAME network?
docker network inspect myapp-net --format '{{range .Containers}}{{.Name}} {{end}}'

# 2. Does the name resolve?
docker exec app ping db          # or: getent hosts db

# 3. Is the target listening on 0.0.0.0 (not only 127.0.0.1)?
docker exec db ss -tlnp

# 4. Full packet path between containers
docker exec app nc -zv db 5432

The three most common failures in practice: containers on different networks (every bare docker run created its own), service listening on internal localhost instead of 0.0.0.0, and a typo in the service name. In that order.

FAQ

Container → host machine? On Linux, special IP host.docker.internal requires flag --add-host=host.docker.internal:host-gateway; Mac/Windows include it natively. Useful for reaching a natively-installed DB during migrations.

Are Docker networks secure? Network isolation is real but does NOT replace authentication: any compromised container on your network can talk to the rest. Minimize shared networks across untrusted projects.

IPv6? Docker supports IPv6 networks enabled explicitly per-network; still advanced-configuration territory unless specifically needed.


Build multi-service stacks with correct names and dependencies using our Docker Compose Generator, free and no sign-up.

Try it without code

Docker Compose Generator

Local stack with common presets.

Open Docker Compose Generator

Built by

Miguel Ángel Colorado Marin (MACM)

Full-Stack Developer · Guadalajara, España

I develop web apps, digital tools and full projects — from design to deployment.

Contact me