Chapter 7: Networking
Topic 40

Default Bridge vs User-Defined Bridge

NetworkingBridge

Docker creates a default bridge network named bridge — the docker0 interface — and attaches every container to it unless you say otherwise. It is the wrong network to use for anything real. The default bridge gives containers no automatic name resolution: they can reach each other only by raw IP address or the deprecated --link flag, and a container's IP changes every time it restarts.

A user-defined bridge — one you create with docker network create — adds an embedded DNS server, so containers find each other by name, and it isolates its members from containers on other networks. The rule for this whole chapter fits on one line: always create a user-defined bridge; never run a multi-container app on the default one.

Default bridge vs user-defined bridge
Default bridge
No automatic DNS — containers reach peers only by raw IP address or the deprecated --link flag, and that IP changes on every restart.
User-defined bridge
Automatic DNS by container name, plus scoped isolation from containers on other networks.

The Default Bridge, and Why It's a Trap

Every container started with no --network flag lands on the default bridge. It works fine for the simplest case — a single container that just needs to reach the internet through NAT. The trap springs the moment you have two containers that must talk. There is no DNS on the default bridge, so web can reach db only at an address like 172.17.0.3 — an address Docker assigns from the subnet at startup and reassigns on the next restart. Hardcode it and the app works until the first docker restart, then breaks for a reason that looks like anything but networking.

--link Is Deprecated

The old workaround for name resolution on the default bridge was --link, which injects /etc/hosts entries into one container pointing at another. It is deprecated for good reasons: it is one-directional, it breaks when the linked container restarts with a new IP, and it survives today only in legacy setups. Do not build on it. If you find yourself reaching for --link, the real answer is a user-defined bridge.

User-Defined Bridges and Automatic DNS

Creating a network is one command, and it changes everything about how the containers find each other.

Create a user-defined bridge and run two containers on it
docker network create driftwood-net

docker run -d --name db --network driftwood-net postgres:16
docker run -d --name web --network driftwood-net driftwood:latest

docker network create driftwood-net makes a bridge with Docker's embedded DNS server (topic 41) attached. Any container on driftwood-net resolves every other container on it by name, so web's database URL is simply db:5432 and stays correct no matter which IP db draws at startup. This single feature — name resolution that survives restarts — is what makes a multi-container app sane to operate.

Isolation by Network

A container can reach only the containers it shares a network with. A user-defined bridge is its own broadcast domain — its own switch — so a container on driftwood-net cannot reach a container on some other network, and vice versa. That gives you isolation the default bridge does not: on the flat default bridge, every container shares one network, so an unrelated container can reach your db by IP. Topic 44 builds real tier isolation on this property; for now the point is that separate networks are walls, and the default bridge has none.

Driftwood on driftwood-net

The three Driftwood containers join one user-defined bridge, driftwood-net. proxy resolves web, web resolves db, and none of them carries a hardcoded IP anywhere — the wiring is written once with names and works on every machine and across every restart. This is the spine of the chapter, and it is exactly the shape Docker Compose (Chapter 8) creates for you automatically: when a tutorial's containers "just find each other by name," it is because Compose quietly made a user-defined network like this one.

Default bridge vs user-defined bridge

Default bridge (docker0) — where containers land with no --network. No automatic DNS, so containers reach each other only by IP or the deprecated --link; all containers share one flat network with no isolation between unrelated apps. Reach for it only for a throwaway single-container run that needs nothing but egress.

User-defined bridge (docker network create driftwood-net) — adds embedded DNS so containers resolve each other by name, isolates its members from containers on other networks, and lets containers attach and detach at runtime. Choose it for anything with more than one container — which is almost everything.

Common Mistakes
  • Running a multi-container app on the default bridge and wiring web to db by IP — the IP changes on the next restart and the app breaks; a user-defined bridge with name resolution avoids it entirely.
  • Reaching for --link to get container name resolution — it is deprecated, one-directional, and fragile across restarts; create a user-defined bridge instead.
  • Assuming the default bridge isolates apps from each other — every container on it shares one flat network, so an unrelated container can reach your db by IP; only separate networks isolate.
  • Believing bare docker run "just works" with names because it did in a tutorial that used Compose — Compose silently creates a user-defined network; docker run on the default bridge has no DNS at all.
Best Practices
  • Create a user-defined bridge with docker network create for every application and attach its containers to it, so name resolution and isolation come for free.
  • Name the network after the app — driftwood-net — so its purpose is obvious in docker network ls and inspect output.
  • Never connect related containers via the deprecated --link; rely on the user-defined bridge's embedded DNS.
  • Let Compose (Chapter 8) create the user-defined network for you in real projects — it does this by default — and reserve the default bridge for throwaway single-container runs.
Comparable tools Podman defaults to a DNS-enabled network via netavark/aardvark-dns, avoiding the default-bridge trap Kubernetes has no flat default — every pod gets a routable address and Services provide names ip link add … type bridge the raw Linux bridge docker0 is, built by hand

Knowledge Check

What does the default bridge network lack that forces you off it for multi-container apps?

  • Automatic name resolution — containers can reach each other only by IP or the deprecated --link
  • Internet egress — containers on the default bridge cannot reach the internet at all, even through host NAT
  • Connectivity — containers on the default bridge are fully isolated from one another at the packet level
  • The ability to publish container ports to the host with the -p flag

Why is --link the wrong way to get name resolution between containers?

  • It is deprecated, one-directional, and breaks when the linked container restarts with a new IP
  • It runs a slow embedded DNS resolver that adds measurable latency to every single name lookup
  • It only works on user-defined bridges, defeating the purpose since those already resolve names
  • It publishes the linked container's ports to the public internet, opening a security hole

What does a user-defined bridge add that the default bridge does not?

  • An embedded DNS server for name resolution, plus isolation from containers on other networks
  • Public IP addresses for each container so they are directly reachable from the internet without NAT
  • The ability to span multiple hosts so containers on different machines connect over one subnet
  • Transparent encryption of all traffic between containers, turned on by default

Why does "always create a user-defined bridge" hold even when a tutorial seemed to resolve names on a bare run?

  • That tutorial almost certainly used Compose, which silently creates a user-defined network with DNS
  • Recent Docker versions quietly added embedded DNS to the default bridge, so the old advice no longer matters
  • The --name flag enables DNS resolution on the default bridge automatically for that container
  • Publishing a port with -p turns on name resolution for every container on that host

You got correct