skip to content

What is Docker's default `bridge` network (docker0), and why does Docker recommend creating your own bridge network instead?

level: juniorimportance: must knowfreq 74%

answer

  1. Two bridges, one is created for you
  2. Which containers share the segment?
  3. Name resolution exists on only one of them
  4. docker0 options are daemon-wide; a created network's are per-network
  5. docker network create, then --network on run

basics

~20 s

Every container started without --network lands on the shared default bridge, docker0, where peers can only be reached by IP address. A bridge you create with docker network create adds name-based discovery, membership-scoped isolation and per-network options.

solid answer

~50 s

The daemon creates a Linux bridge called `docker0` at start-up, and any container run without `--network` is attached to it through a veth pair, taking an address from the bridge's subnet. That makes docker0 one shared segment for every container on the host that did not ask for anything else, and on it containers can only address each other by IP — there is no name resolution. A network you create with `docker network create` uses exactly the same plumbing on a bridge of its own, but adds three things: Docker's embedded resolver, so attached containers reach each other by container name or alias; a membership boundary, so containers on other networks cannot reach in; and per-network options set at create time (`--internal`, `--opt com.docker.network.driver.mtu`, `--opt com.docker.network.bridge.enable_icc`) instead of daemon-wide settings that need a dockerd restart. The default bridge is kept for backwards compatibility and one-off commands.

code

bash · 8 lines
bash
# Default bridge: no name resolution between containers
docker run -d --name digest-redis redis:7
docker run --rm ruby:3.3-slim-bookworm getent hosts digest-redis || echo "no such name"

# A bridge you create: the container name resolves
docker network create digest-net
docker run -d --name digest-cache --network digest-net redis:7
docker run --rm --network digest-net ruby:3.3-slim-bookworm getent hosts digest-cache

go deeper

for a junior

Be ready to say that a container with no --network flag joins the default bridge, and that containers there can only reach each other by IP. Know the two commands: docker network create, then docker run --network.

for a middle

Explain the mechanics: a veth pair per container enslaved to docker0 or to br-<id>, addresses from that bridge's subnet, and the embedded resolver that only user-defined networks get. Know that a created network's options are fixed at create time.

for a senior

Show the operational consequences: docker0 is one shared segment for unrelated workloads, its settings are daemon-wide and need a dockerd restart, and network membership is not a substitute for controlling published ports.

for a principal

Own the convention for a fleet: one network per stack, never the default bridge for anything long-lived, and a documented place for per-network options so nobody reaches for a daemon-wide change to solve one team's problem.

## The three networks Docker gives you for free On a fresh engine, `docker network ls` lists three predefined networks: `bridge`, `host` and `none`. The `bridge` entry is what people call the **default bridge**. When dockerd starts it creates a Linux bridge device on the host named `docker0`, gives it an address, and attaches every container that is started without an explicit `--network` flag to it. None of the three predefined networks can be removed. ## What attachment actually looks like When a container joins any bridge network, the daemon creates a veth pair: one end stays on the host and is enslaved to the bridge device, the other is moved into the container's network namespace where it appears as `eth0`. The container gets an address out of that bridge's subnet and a default route pointing at the bridge's own address. A network you create — `docker network create digest-net` — gets exactly the same plumbing; the only visible difference on the host is that the bridge device is named `br-<first characters of the network id>` rather than `docker0`. So the *mechanism* is identical. What differs is scope, discovery, configuration and lifecycle. ## Difference 1 — who is on the segment The default bridge is a single shared segment. Every container on the host that did not ask for a network is a peer of every other one: your database, a colleague's debugging shell, a job container someone left running. A network you create is a membership list. Only the containers you attach are on it, and a container attached to a different bridge cannot reach in, because the daemon does not route between the two bridges. That is the isolation people mean when they say "put it on its own network". ## Difference 2 — discovery On a user-defined bridge Docker gives each attached container an embedded resolver, so container names and network aliases resolve to the peer's current address. On the default bridge there is no such resolution: you address peers by IP, or historically by declaring a `--link`. Since a container's address is handed out at start time and can be different after the next start, "IP only" is a fragile way to wire anything together. The internals of that resolver are a separate subject; the bridge-level fact to carry into an interview is that the feature exists only on networks you create. ## Difference 3 — configuration granularity Settings for docker0 are daemon-wide. `mtu`, `icc`, `bip` and the address pools live in `/etc/docker/daemon.json`, and changing one means restarting dockerd, which touches every container on the host. A network you create takes its settings at creation time and keeps them: `--subnet`, `--gateway`, `--internal`, and `--opt` keys such as `com.docker.network.driver.mtu`, `com.docker.network.bridge.enable_icc`, `com.docker.network.bridge.name` and `com.docker.network.bridge.enable_ip_masquerade`. Those options are immutable afterwards — to change one you create a replacement network and re-attach the containers — but the blast radius of a change is one network, not the whole engine. ## Difference 4 — lifecycle A running container can be attached to a network you created with `docker network connect`. For the default bridge, Docker's guidance is to recreate the container with `--network` rather than shuffling it around, because its networking was decided at creation. ## Difference 5 — legacy versus current `--link` only ever made sense on the default bridge, as a workaround for the missing name resolution. It is documented as legacy. On a user-defined network you simply use the peer's name. ## The failure everybody meets A stack that worked as a Compose project is re-run by hand as three `docker run` commands. Under Compose the containers were on a network of their own; run by hand they land on docker0, and the first thing that breaks is name resolution — a Ruby email-digest builder that opens `redis://digest-redis:6379` now dies with `getaddrinfo: Name or service not known`. The fix is a network, not hand-written `/etc/hosts` entries and not `--link`: ``` docker network create digest-net docker run -d --name digest-redis --network digest-net redis:7 docker run -d --name digest-worker --network digest-net digest:1.4 ``` ## What isolation does and does not mean Two caveats keep coming up as follow-ups. First, attaching to your own network isolates you from containers on *other* networks; it does not firewall peers on the *same* network from each other — that needs inter-container communication disabled explicitly. Second, none of this changes port publishing: `-p 8080:4567` maps a host port the same way whichever bridge the container is on, so "it is on a private network" is not an argument that it is unreachable from outside the host. ## When the default bridge is fine Throwaway commands. `docker run --rm -it ruby:3.3-slim-bookworm irb` does not need a network of its own. The moment a second container has to find the first one by name, create a network.

  • Can you change the subnet or MTU of the default bridge the way you can for a network you create?
    Not per network — docker0 is configured by the daemon, through keys such as `bip`, `mtu` and `icc` in `/etc/docker/daemon.json`, and the change only takes effect after restarting dockerd, which affects every container on the host. A network you create takes `--subnet`, `--gateway` and `--opt` values at creation time; they are fixed for that network's life, so changing one means creating a replacement network and re-attaching.
  • Can you move a container off the default bridge without recreating it?
    You can attach a running container to another network at any time with `docker network connect`, so it ends up on both. Docker's own guidance for getting it off the default bridge is to stop it and recreate it with `--network`, because its networking — address, resolver behaviour, any legacy links — was decided when the container was created.
  • Does putting containers on their own network change how you publish ports?
    No. `-p` publishes a container port on a host port regardless of which bridge the container is attached to, and it stays reachable from outside the host. The network only decides which other containers can address it directly. Treating "it is on a private network" as protection from external traffic is a common mistake.

The default bridge is the shared office Wi-Fi that every device joins by default and where you have to know each other's IP; a network you create is a small private VLAN with its own directory and its own settings.

saying these in an interview costs you the question

  • Says containers on the default bridge resolve each other by name
  • Thinks docker0 and a created bridge differ only in the device name
  • Believes a user-defined network hides published ports from outside
  • Claims --link is still needed on a user-defined network
  • Assumes a created network's options can be edited after creation
  • Cannot say which network a container without --network joins

context