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:

AspectDefault Bridge
Network namebridge
Subnet172.17.0.0/16 (default)
Internal DNS❌ None
Name resolutionOnly via IP or the legacy link
IsolationAll containers on the same bridge
Port mappingSupports -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.

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:

AspectUser-Defined Bridge
NameFree (e.g. app-network, backend-net)
SubnetAutomatic or custom (--subnet)
Internal DNS✅ Automatically active
Name resolutionContainer names AND aliases
IsolationPer network, more isolated
Containers on multiple networks✅ Supported
Port mappingSupports -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 #

FeatureDefault BridgeUser-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 forExperiments, single containersMulti-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| veth

Packets 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/16 range).
  • 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 --> NET

When a container sends a packet to the internet:

  1. The packet leaves the container via eth0.
  2. The veth forwards it to the bridge.
  3. The bridge looks up the destination — not local, so it forwards to the routing table.
  4. The routing table decides the host’s eth0 interface.
  5. iptables performs MASQUERADE — the source IP becomes 192.168.1.10.
  6. 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 response

You 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:80 without a host IP binds to 0.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 to 127.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 --> DB

The 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 show or ip link show on 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) →

About | Author | Content Scope | Editorial Policy | Privacy Policy | Disclaimer | Contact