Most Docker networking confusion comes from not knowing which of the three common modes is actually in play for a given container, and debugging the wrong layer as a result. Here's what each mode actually does, in the order you're likely to need them.

Bridge: the default, and why

Bridge is what you get without specifying anything else. Docker creates a private virtual network on the host, gives each container its own IP on that network, and uses iptables rules (or nftables, depending on your setup) to handle NAT between the container network and the outside world.

Creating and using a custom bridge network

docker network create app-network
docker run --network=app-network --name=api myapp:latest

A custom bridge network (rather than the default one Docker creates automatically) gives containers automatic DNS resolution by container name — something the default bridge network doesn't provide. This alone is reason enough to always create a named network rather than relying on the default.

Host: when isolation isn't worth the cost

Host mode skips network isolation entirely — the container shares the host's network stack directly, with no port mapping needed:

docker run --network=host monitoring-agent:latest

This trades isolation for raw performance and simplicity, which is the right call for things like network monitoring agents that genuinely need to see the host's actual network interfaces, or high-throughput services where the small NAT overhead of bridge mode is measurable. For a typical web application, it's rarely worth giving up the isolation bridge mode provides.

!

A container in host mode can bind to any port on the host directly, with no mapping layer in between. Port conflicts between containers, or between a container and a host process, become possible in a way bridge mode prevents by design.

Overlay: networking across multiple hosts

Overlay networks solve a different problem: letting containers on different physical or virtual hosts communicate as if they were on the same network. This only matters once you're running Docker Swarm or a similar multi-host setup — a single-host Compose deployment never needs this mode.

docker network create --driver=overlay --attachable cluster-net

Under the hood, overlay networking uses VXLAN to tunnel container traffic between hosts, which adds a small amount of latency compared to same-host bridge traffic — usually negligible, but worth knowing about if you're chasing a tail-latency issue in a multi-host setup.

Container DNS and service discovery

On a custom bridge or overlay network, Docker's embedded DNS server resolves container names to their current IP automatically. This is what lets a Compose file reference a database container by service name rather than a hardcoded IP that would break on every restart:

services:
  api:
    environment:
      DATABASE_URL: postgres://db:5432/app
  db:
    image: postgres:16

The hostname db in that connection string resolves through Docker's internal DNS to whatever IP the database container currently has — no hardcoding, and no breakage if the container restarts and gets reassigned a different address.

Debugging connectivity issues

When two containers can't reach each other, the first question is always: are they actually on the same network? It's a more common mistake than it sounds, especially in Compose files with multiple network definitions.

Checking which networks a container is on

docker inspect api --format='{{json .NetworkSettings.Networks}}'

If they're on the same network and still can't connect, the next step is checking whether the destination container's application is actually listening on the expected port, and whether it's bound to 0.0.0.0 rather than 127.0.0.1 — a service bound only to localhost inside its own container is unreachable from anywhere else, network configuration notwithstanding.

docker exec api netstat -tlnp

Nine times out of ten, a Docker networking issue traces back to one of these two things — wrong network, or wrong bind address — rather than anything more exotic.

ML

Mikko Laaksonen

DevOps engineer based in Tampere, Finland. Writes about Linux, Docker, databases, and web design.

More about Mikko →