Bridge Network #
Among all the network drivers available in Docker, bridge is the one you’ll encounter most often — possibly without even realizing it. Every time you run docker run without a --network argument, the container automatically joins the default bridge. Behind that command’s simplicity is an elegant architecture: a virtual switch, veth pairs, IPAM, and iptables NAT working together to give containers isolated but functional network access.
Bridge is the foundation you must understand before jumping to other drivers (host, overlay, macvlan). Once you understand bridge, every other driver feels like a variation on the same concept — just differing in isolation level and scope.
This article covers bridges in depth: how they work at the OS level, the difference between the default bridge and user-defined bridges, why the latter is far more recommended, and how bridges interact with NAT, port mapping, and DNS. By the end, you’ll be able to design safe, scalable multi-container network topologies.
What Is a Bridge Network? #
A Docker bridge network is a virtual layer-2 switch running inside the host. This switch connects many containers at once and forwards Ethernet packets between them. Conceptually, a bridge = a virtual network switch, and containers = devices plugged into that switch.
flowchart LR
subgraph Bridge["docker0 (virtual bridge)"]
direction LR
B("docker0<br/>172.17.0.1")
end
C1["Container A<br/>172.17.0.2"] ---|veth| Bridge
C2["Container B<br/>172.17.0.3"] ---|veth| Bridge
C3["Container C<br/>172.17.0.4"] ---|veth| Bridge
Bridge ---|iptables NAT| Net[Host network]
Net --- Internet("Internet")Main bridge network characteristics:
- Containers get private IPs on a Docker-managed subnet (e.g.
172.17.0.0/16). - Containers on the same bridge can talk directly (L2 forwarding).
- Containers on different bridges can’t talk without explicit routing.
- Outbound access (to the internet or LAN) goes through NAT in iptables.
- Inbound access (from the host or outside) only via port mapping (
-p).
Bridges aren’t new in the Linux world. They’ve existed for a long time as brctl / iproute2 features. Docker just packages them neatly, adding iptables, IPAM, and internal DNS server integration.
Why is it called a “bridge”? Because it mimics a physical network bridge’s behavior (a layer-2 switch). Packets are forwarded by MAC address, not IP. That’s what makes inter-container communication very fast for internal traffic.
Default Bridge vs User-Defined Bridge #
Docker provides two kinds of bridges, and the difference between them is one of the most important things to understand.
The Default Bridge (bridge)
#
When the Docker Engine first runs, it creates one built-in bridge named bridge (on the host, its Linux interface is named docker0). All containers run without --network automatically join this bridge.
# Containers automatically join the default bridge
docker run -d --name web nginx
# See which network the container is connected to
docker inspect web --format '{{.NetworkSettings.Networks}}'
Default bridge characteristics:
| Aspect | Default Bridge |
|---|---|
| Network name | bridge |
| Subnet | 172.17.0.0/16 (default) |
| Internal DNS | ❌ None |
| Name resolution | Only via IP or the legacy link |
| Isolation | All containers on the same bridge |
| Port mapping | Supports -p |
The biggest limitation: no internal DNS. The web container can’t reach the db container by the hostname db. You must know its IP address.
# Old way (not recommended): hardcoded IP
ping 172.17.0.3
Anti-pattern: Leaving containers on the default bridge for multi-container applications. No DNS, no per-project isolation, all containers share one subnet. Fine for experiments; for serious applications, don’t.
User-Defined Bridges (Recommended) #
You can create your own bridge with docker network create. This is called a user-defined bridge — and it’s what you should use for almost every serious scenario.
# Create a user-defined bridge
docker network create app-network
# Run containers on that network
docker run -d --name web --network app-network nginx
docker run -d --name db --network app-network postgres
Now web can reach db simply by the hostname db:
docker exec web ping db
# PING db (172.18.0.3): 56 data bytes
# 64 bytes from 172.18.0.3: seq=0 ttl=64 time=0.087 ms
User-defined bridge characteristics:
| Aspect | User-Defined Bridge |
|---|---|
| Name | Free (e.g. app-network, backend-net) |
| Subnet | Automatic or custom (--subnet) |
| Internal DNS | ✅ Automatically active |
| Name resolution | Container names AND aliases |
| Isolation | Per network, more isolated |
| Containers on multiple networks | ✅ Supported |
| Port mapping | Supports -p |
Best practice: For every multi-container project, always create a user-defined bridge. Even for just two containers, be explicit. This trains you to think about network topology, not just casual docker run.Direct Comparison #
| Feature | Default Bridge | User-Defined Bridge |
|---|---|---|
| Automatic DNS | ❌ | ✅ |
| Per-project isolation | ❌ (all on one bridge) | ✅ (separate networks) |
| Container on many networks | ❌ | ✅ |
--link (legacy) | ✅ | ❌ (not needed) |
| Custom subnet / gateway | ❌ | ✅ |
| Best for | Experiments, single containers | Multi-container applications |
How Bridges Work at the OS Level #
Now to the more technical part. Understanding what happens at the OS level helps you debug when problems arise and understand why certain configurations don’t behave as expected.
1. The Linux Bridge Interface #
When a user-defined bridge is created, Docker creates a bridge interface on the host:
docker network create app-network
ip link show
# br-abc123: <BROADCAST,MULTICAST,UP,LOWER_UP>
# inet 172.18.0.1/16 brd 172.18.255.255
The br-abc123 interface is the bridge itself. It has an IP (the gateway) and becomes the connection point for all containers on the network.
2. Network Namespaces #
Each container runs in a separate network namespace. This namespace has:
- Its own interface (usually
eth0). - Its own routing table.
- Its own loopback
lo.
Containers on the same bridge network live in different namespaces, but are connected via veth pairs to the same bridge.
3. Virtual Ethernet (veth) Pairs #
Docker creates a veth pair for every container — two virtual interfaces always connected:
- The first end goes into the container namespace (appearing as
eth0). - The second end stays in the host namespace, plugged into the bridge.
flowchart LR
subgraph CN["Container Namespace"]
eth0["eth0"]
end
subgraph HN["Host Namespace"]
veth["veth-xxx@br-net"]
end
eth0 <-->|veth| vethPackets the container sends via eth0 appear at the host-side veth end, then are forwarded by the bridge to the destination container.
4. IPAM (IP Address Management) #
Docker automatically manages container IP allocation via the IPAM driver. For each network, IPAM:
- Determines the subnet (default: random in the
172.x.0.0/16range). - Assigns a unique IP to each container.
- Determines the gateway.
You can override:
docker network create \
--subnet=192.168.10.0/24 \
--gateway=192.168.10.1 \
--ip-range=192.168.10.0/25 \
custom-net
5. NAT and iptables #
So containers can reach the internet, Docker adds an iptables MASQUERADE rule:
# View the NAT rules (simplified)
iptables -t nat -L POSTROUTING
# Chain POSTROUTING
# -A POSTROUTING -s 172.18.0.0/16 ! -o br-abc123 -j MASQUERADE
Meaning: packets from the 172.18.0.0/16 subnet exiting not through the bridge itself get MASQUERADEd (their source is replaced with the host IP). As a result, from the internet’s perspective, the host is visible — not the container.
The Complete OS-Level Scheme #
flowchart LR
subgraph CT["Container Namespace"]
E[eth0: 172.18.0.2]
end
subgraph HT["Host Namespace"]
V["veth-xxx@br-abc"]
B[br-abc123: 172.18.0.1]
HV[eth0: 192.168.1.10]
end
subgraph K["Kernel"]
RT[Routing Table]
IPT[iptables NAT]
end
NET[Internet]
E <-->|l2| V
V --> B
B --> RT
RT --> IPT
IPT --> HV
HV --> NETWhen a container sends a packet to the internet:
- The packet leaves the container via
eth0. - The veth forwards it to the bridge.
- The bridge looks up the destination — not local, so it forwards to the routing table.
- The routing table decides the host’s
eth0interface. - iptables performs MASQUERADE — the source IP becomes
192.168.1.10. - The packet exits to the internet.
When the reply arrives: the reverse process. The packet gets DNAT-ed back to the container IP (172.18.0.2) and forwarded through the bridge.
Port Mapping on Bridges #
Bridge networks are private — containers inside them aren’t automatically reachable from the host or the internet. To open access, you need port mapping (-p / --publish).
docker run -d --name web -p 8080:80 nginx
Meaning: port 8080 on the host is forwarded to port 80 in the container.
sequenceDiagram
participant U as User
participant H as Host:8080
participant I as iptables DNAT
participant B as Bridge
participant C as Container:80
U->>H: GET localhost:8080
H->>I: Packet arrives
I->>I: DNAT: 8080 -> 172.18.0.2:80
I->>B: Forward
B->>C: Reaches the container
C-->>B: Response
B-->>I: Back
I-->>H: Reverse SNAT
H-->>U: HTTP responseYou can also bind to a specific host IP:
# Only localhost can access
docker run -p 127.0.0.1:8080:80 nginx
# Specific IP (e.g. the host's LAN IP)
docker run -p 192.168.1.10:8080:80 nginx
# Random host port
docker run -p 80 nginx
Danger:-p 8080:80without a host IP binds to0.0.0.0— meaning every host interface accepts connections on port 8080. If the host has a public IP, this port is exposed to the internet. For databases or internal services, always bind to127.0.0.1.
Isolation: Bridges Provide Natural Boundaries #
One of the bridge network’s often-overlooked strengths is its default isolation. Containers on bridge A can’t talk to containers on bridge B without extra configuration.
docker network create frontend
docker network create backend
docker run -d --name web --network frontend nginx
docker run -d --name db --network backend postgres
Now web can’t reach db over the network (different networks). To connect them, you can:
# Connect the web container to backend too
docker network connect backend web
Or, more safely: create a separate container acting as a gateway, e.g. an API service connected to both networks.
flowchart LR
subgraph Front["frontend network"]
W[web]
API[api]
end
subgraph Back["backend network"]
API2[api - same container]
DB["(db)"]
end
W --> API
API <--> API2
API2 --> DBThe api container has two interfaces — one on frontend, one on backend. It becomes a bridge you can control, observe, and audit. The database stays truly hidden from the frontend.
Bridges in Docker Compose #
Docker Compose always creates a user-defined bridge for a project by default. You don’t need a manual network declaration — Compose handles it.
# docker-compose.yml
services:
web:
image: nginx
db:
image: postgres
api:
image: my-api
Without a networks declaration, Compose automatically:
- Creates one network named
<project>_default. - Connects all services to that network.
- Enables internal DNS based on service names.
As a result, web can talk to api via http://api:8080, and api can talk to db via db:5432 — without extra configuration.
If you need custom or multi-networks:
version: "3.9"
services:
web:
image: nginx
networks:
- frontend
api:
image: my-api
networks:
- frontend
- backend
db:
image: postgres
networks:
- backend
networks:
frontend:
backend:
internal: true # no outbound internet access
internal: true makes the network truly isolated from the outside — no outbound NAT, no internet access. Good for backends that should only talk to internal services. Cheap defense in depth without extra config.When Bridge Networks Are the Right Choice #
Bridge networks fit:
- Local development on a single host.
- Multi-container applications (microservices) on one host.
- CI/CD pipelines running tests in containers.
- Backend + database on one server or VM.
- Single-host production deployments for small-to-medium applications.
Bridge networks are less suitable for:
- Multi-host deployments (use overlay for Swarm or K8s).
- Extreme network performance (use host networking).
- No-network sandboxes (use none).
- Direct LAN integration (use macvlan).
Anti-Patterns and Solutions #
Here are the three most common bridge network traps, and how to fix them.
1. Leaving Containers on the Default Bridge #
# ✗ Anti-pattern: containers on the default bridge, must use IPs
docker run -d --name db postgres
docker run -d --name api my-api
# api can't use "db", must use an IP
# ✓ Solution: user-defined bridge, automatic DNS
docker network create app-net
docker run -d --name db --network app-net postgres
docker run -d --name api --network app-net my-api
# api can use "db:5432"
2. Hardcoding Container IPs #
# ✗ Anti-pattern: hardcoded IP
environment:
DATABASE_HOST: 172.18.0.3 # can change on restart
# ✓ Solution: use the service name
environment:
DATABASE_HOST: db # DNS resolves automatically
3. Using Host Networking for Microservices #
# ✗ Anti-pattern: host networking for many services
services:
web:
network_mode: host
db:
network_mode: host
# Port conflicts, no isolation
# ✓ Solution: user-defined bridge
services:
web:
networks: [app]
db:
networks: [app]
networks:
app:
Essential Bridge Network Commands #
Before moving on, memorize these commands. They’ll be your daily companions when working with bridge networks.
# List all networks (including default and custom bridges)
docker network ls
# Inspect one network's details
docker network inspect app-network
# Shows: subnet, gateway, connected containers, IP per container
# Create a user-defined bridge with full options
docker network create \
--driver bridge \
--subnet 192.168.50.0/24 \
--gateway 192.168.50.1 \
--ip-range 192.168.50.0/25 \
my-custom-bridge
# Connect an already-running container to another network
docker network connect app-network existing-container
# Disconnect from a network
docker network disconnect app-network existing-container
# Remove a network (make sure no containers are connected)
docker network rm app-network
# Prune: remove all unused networks
docker network prune
Useful debugging tricks:
docker network inspect <name>→ see which containers are connected and their IPs.brctl showorip link showon the host → see the bridge interface and plugged-in veths.iptables -t nat -L -n | grep <network-name>→ see the NAT rules for that network.
Summary #
- Bridge networks are virtual layer-2 switches connecting containers on one host. They’re the most common driver and the most recommended for single-host applications.
- The default bridge (
docker0) fits experiments, but has no internal DNS and all containers share one subnet.- User-defined bridges (
docker network create my-net) are the serious choice: internal DNS by container name, per-project isolation, and flexible subnet/gateway configuration.- Behind the scenes, bridges work with four components: namespaces (isolation), veth pairs (virtual cables), the bridge interface (switch), and iptables (NAT + firewall).
- Port mapping (
-p) opens host-to-container access. Without-p, containers can only talk to other containers on the same network — not the host or the internet.- Isolation between bridges is a natural security boundary. Separate frontend, backend, and database into different networks for defense in depth.
- Docker Compose always creates a user-defined bridge automatically. For multi-network setups or
internal: true, declare networks manually.- Main anti-patterns: staying on the default bridge, hardcoding IPs, using host networking for microservices.
- Bridges fit: local development, single-host deployment, small-to-medium microservices, CI/CD. Less suitable for: multi-host, extreme performance, sandboxes.
← Previous: Overview Next: NAT (Network Address Translation) →