Docker & ContainersVII · NetworkingBridge networking
Bridge networks — the default and how to use them well
What you'll learn
- Create user-defined bridge networks
- Configure MTU, subnet, gateway, and iptables settings
- Choose the right network driver per workload
Prerequisites
Verified against Docker Engine 29.x · Docker Engine 28.x · Docker Compose 2.x · containerd 2.x · runc 1.2.x · BuildKit 0.20+ · Linux kernel 5.15+ · Ubuntu 24.04 LTS · Debian 12 (Bookworm) · 2026-08-09
The default bridge network exists for legacy compatibility with the very first versions of Docker. It has a number of problems that user-defined bridges fix. You should almost always use a user-defined network.
The problems with the default bridge
- No automatic DNS resolution between containers. Containers on the default bridge can reach each other by IP only.
- All containers are on the same L2 segment. No isolation between unrelated workloads.
- Container startup order matters. Network attachments are not declarative.
User-defined networks solve all three.
Creating a user-defined bridge
docker network create --driver bridge \
--subnet 10.10.0.0/24 \
--gateway 10.10.0.1 \
--opt com.docker.network.bridge.name=br-app \
app-net
The --driver bridge is explicit; it is the default. The
--subnet and --gateway define the addressing; --opt com.docker.network.bridge.name gives the bridge a memorable name
(br-app).
docker network ls
docker network inspect app-net
Connecting containers
docker run --network app-net --name web -d nginx
docker run --network app-net --name api -d myapi
The two containers can reach each other by name (web,
api). Docker’s embedded DNS resolves the names to the
containers’ IPs.
Network scopes
A container can be attached to multiple networks:
docker network connect app-db api
# api is now on both app-net and app-db
This is useful for an API container that needs to talk to both a frontend network and a database network.
Bridge options
--driver bridge: bridge
--subnet 10.10.0.0/24: CIDR
--gateway 10.10.0.1: IP
--opt com.docker.network.bridge.name=br-app: string
--opt com.docker.network.bridge.enable_icc=true: bool
--opt com.docker.network.bridge.enable_ip_masquerade=true: bool
--opt com.docker.network.bridge.inhibit_ipv4=true: bool
--opt com.docker.network.driver.mtu=1500: int
--opt com.docker.network.container_iface_prefix=eth: string
--ipv6: true01
--driver bridge= bridgeThe Linux bridge driver.
Production: Default for Linux. Use macvlan for L2 adjacency.
02
--subnet 10.10.0.0/24= CIDRSubnet for the bridge.
Production: Use private RFC1918 ranges that do not conflict with corporate networks.
03
--gateway 10.10.0.1= IPThe gateway (host-side bridge IP).
Production: Pick a stable, predictable address.
04
--opt com.docker.network.bridge.name=br-app= stringCustom bridge interface name.
Production: Useful for matching iptables rules and DNS.
05
--opt com.docker.network.bridge.enable_icc=true= boolInter-container communication. Disable for strict isolation.
Production: Disable by default in multi-tenant environments.
06
--opt com.docker.network.bridge.enable_ip_masquerade=true= boolMasquerade outgoing traffic. Required for outbound access.
Production: Enable by default.
07
--opt com.docker.network.bridge.inhibit_ipv4=true= boolDo not assign the gateway IPv4 address to the bridge interface itself.
Production: Only for setups where an external router owns the gateway address.
08
--opt com.docker.network.driver.mtu=1500= intMTU of the bridge. Note the key is driver.mtu, not bridge.mtu — the latter is silently ignored.
Production: Set 1450 for VXLAN overlays, 9000 for jumbo frames on supported hardware.
09
--opt com.docker.network.container_iface_prefix=eth= stringPrefix for the interface name inside the container.
Production: Leave at the default unless a workload hard-codes an interface name.
10
--ipv6= trueEnable IPv6 on the bridge.
Production: Pass a second --subnet with an IPv6 CIDR to pin the range: --ipv6 --subnet 2001:db8:1::/64. There is no --subnet6 flag.
DNS resolution
The embedded DNS server runs at 127.0.0.11 inside every
container. It resolves:
- Container names (
web,api). - Service names in compose (
web,api,db). - Aliases set with
--network-alias. - External names via the host’s resolver.
A container on a user-defined network has an /etc/resolv.conf
pointing at 127.0.0.11. A container on the default bridge does
not: it receives a copy of the host’s resolver configuration
instead, with no embedded DNS in the path at all. That single
difference is the subject of the next section.
There is no bridge driver option for upstream resolvers.
com.docker.network.bridge.dns_servers is not in the driver’s
option set and configures nothing. Set resolvers per container with
--dns, or host-wide in daemon.json:
docker run --dns 192.0.2.10 --dns-search corp.example.com --network app-net myorg/app:1.0.0
{
"dns": ["192.0.2.10", "192.0.2.11"],
"dns-search": ["corp.example.com"]
}
The embedded resolver at 127.0.0.11 still answers container names
first and forwards everything else to whichever upstreams you
configured.
CONTAINER=migrator
docker inspect --format 'networks: {{range $k, $v := .NetworkSettings.Networks}}{{$k}} {{end}}' "$CONTAINER"
docker exec "$CONTAINER" grep ^nameserver /etc/resolv.confnetworks: bridge
nameserver 192.0.2.10Illustrative output
networks: bridge with a nameserver that is not 127.0.0.11 is the
broken case. The working case looks like this instead:
networks: myproject_default
nameserver 127.0.0.11
NETWORK=myproject_default
TARGET=db
docker run --rm --network "$NETWORK" busybox nslookup "$TARGET" 2>&1 | tail -5Server: 127.0.0.11
Address: 127.0.0.11:53
Name: db
Address 1: 172.19.0.3 db.myproject_defaultIllustrative output
Run the identical command with --network bridge and it returns
server can't find db: NXDOMAIN. Two networks, one command, two
different answers — that is a verification that can fail, which is
what makes it worth running.
MTU
The default MTU is 1500. If you have a tunnel (VXLAN, WireGuard, GRE) between hosts, the effective MTU drops. The standard adjustment:
docker network create --opt com.docker.network.driver.mtu=1450 overlay-net
The key is com.docker.network.driver.mtu. The bridge driver’s own
options are namespaced com.docker.network.bridge.* — name,
enable_icc, enable_ip_masquerade, host_binding_ipv4,
inhibit_ipv4, gateway_mode_ipv4, gateway_mode_ipv6 — but MTU
is not one of them, and com.docker.network.bridge.mtu is a common
enough typo to be worth checking for by hand. Do not assume a wrong
key would have been rejected loudly; confirm the MTU the kernel
actually applied:
NETWORK=overlay-net
docker network inspect "$NETWORK" --format '{{json .Options}}'
BR=$(docker network inspect "$NETWORK" --format 'br-{{slice .Id 0 12}}')
ip -o link show "$BR" | grep -o 'mtu [0-9]*'{"com.docker.network.driver.mtu":"1450"}
mtu 1450Illustrative output
A mtu 1500 in that second line, against an option that says 1450,
is the signature of a mistyped key. The failure mode this prevents is
nasty: MTU mismatches do not break connectivity, they break large
packets, so the TCP handshake succeeds, small responses work, and
only large payloads hang. It presents as “the API works but file
uploads time out”.
A smaller MTU is the cost of the tunnel’s overhead. Containers negotiate TCP MSS automatically; UDP applications may need explicit configuration.
Knowledge check
Knowledge check · 6 questions
Q1. Two containers on the same user-defined bridge network communicate by:
Q2. Containers on the default `bridge` network can resolve each other by container name.
Q3. Which command creates a user-defined bridge network with a 24-bit subnet?
Q4. A container on `bridge-A` cannot reach a container on `bridge-B` even though both are on the same host. Why?
Q5. A script runs `docker run --rm myorg/migrator` and the container fails with `could not resolve host: db`. The same image resolves `db` fine under Compose. What is the cause?
Q6. Which network-create option key actually sets the MTU of a Docker bridge network?
Passing score: 75%. Answers are checked in this browser.