Docker Networking 2025 — Bridge, Host, Overlay, and Macvlan Explained

Sanjeev SharmaSanjeev Sharma
7 min read

Advertisement

Introduction

Why This Matters

Networking is the invisible layer that makes or breaks containerized applications. Developers who do not understand Docker networking spend hours debugging "connection refused" errors, wondering why container A cannot reach container B even though both are "running." Production incidents often trace back to misconfigured network policies, unexpected port exposure, or cross-host routing failures.

Understanding Docker network drivers is also the foundation for Kubernetes networking. Pod-to-pod communication, service discovery via CoreDNS, and NetworkPolicy all build on the same concepts. Engineers who understand bridge networking, DNS resolution, and overlay networks transition to Kubernetes networking much faster than those who treat networking as a black box.

Docker Network Drivers Overview

Docker ships with five built-in network drivers:

DriverUse Case
bridgeDefault for single-host container communication
hostShare host network stack (no isolation)
overlayMulti-host communication in Docker Swarm
macvlanContainers need a MAC address on the physical network
noneComplete network isolation
# List all networks
docker network ls
 
# NETWORK ID     NAME      DRIVER    SCOPE
# abc123         bridge    bridge    local
# def456         host      host      local
# ghi789         none      null      local

Bridge Networking (Default)

The bridge driver creates a private internal network on the Docker host. Containers on the same bridge can communicate; external access requires port mapping.

# Default bridge: containers communicate by IP only (not hostname)
docker run -d --name web nginx
docker run -d --name api myapp
 
# Containers on the default bridge cannot resolve each other by name
# Must use IP: docker inspect web | grep IPAddress
 
# User-defined bridge: containers resolve each other by container name
docker network create app-network
docker run -d --network app-network --name web nginx
docker run -d --network app-network --name api myapp
 
# Now 'api' can reach 'web' by hostname 'web'
docker exec api curl http://web:80

Key difference: The default bridge network does not support DNS resolution by container name. User-defined bridge networks do. Always create user-defined networks for production containers.

# Create a bridge network with custom subnet
docker network create \
  --driver bridge \
  --subnet 172.20.0.0/16 \
  --gateway 172.20.0.1 \
  --ip-range 172.20.240.0/20 \
  app-network
 
# Assign a static IP to a container
docker run -d \
  --network app-network \
  --ip 172.20.240.10 \
  --name db postgres:16

Port Mapping and Exposure

# Map container port 3000 to host port 8080
docker run -d -p 8080:3000 myapp
 
# Map to all interfaces (default)
docker run -d -p 0.0.0.0:8080:3000 myapp
 
# Map to a specific interface only (more secure)
docker run -d -p 127.0.0.1:8080:3000 myapp
 
# Map multiple ports
docker run -d -p 80:80 -p 443:443 nginx
 
# Map to a random host port
docker run -d -p 3000 myapp
docker port myapp  # shows which host port was assigned

EXPOSE in a Dockerfile documents which ports the container uses but does NOT publish them. -p at runtime is required to make ports accessible from outside the container.

Host Networking

The host driver removes network isolation — the container shares the host's network stack directly:

docker run -d --network host nginx
# nginx now listens on the host's port 80 directly
# No port mapping needed (or possible)

Use cases: performance-critical applications where the NAT overhead of bridge networking matters, or tools that need to monitor host network interfaces (packet sniffers, network scanners).

Do not use host networking for regular application containers in production — it bypasses container network isolation.

DNS Resolution in Docker

Docker's embedded DNS resolver (127.0.0.11) handles name resolution for user-defined networks:

# Check DNS inside a container
docker exec -it api cat /etc/resolv.conf
# nameserver 127.0.0.11
# options ndots:0
 
# Test resolution
docker exec api nslookup db
# Server: 127.0.0.11
# Name: db
# Address: 172.20.0.3

In Docker Compose, all services are automatically on the same network and resolve by service name:

services:
  api:
    image: myapp
    environment:
      # 'db' resolves to the postgres container
      - DATABASE_URL=postgres://user:pass@db:5432/mydb
 
  db:
    image: postgres:16

Network Isolation Between Service Tiers

Separate frontend, application, and database tiers onto different networks:

version: '3.8'
 
services:
  nginx:
    image: nginx:alpine
    networks:
      - frontend
    ports:
      - "80:80"
 
  api:
    build: .
    networks:
      - frontend   # reachable from nginx
      - backend    # can reach the database
    # NOT exposed on host
 
  db:
    image: postgres:16-alpine
    networks:
      - backend    # isolated from nginx
    # No ports exposed to host
 
networks:
  frontend:
    driver: bridge
  backend:
    driver: bridge
    internal: true  # no external routing; purely internal

The internal: true flag prevents containers on backend from reaching the internet — database containers have no outbound internet access.

Connecting to External Networks

# Connect a running container to an additional network
docker network connect app-network container_name
 
# Disconnect a container from a network
docker network disconnect app-network container_name
 
# Inspect network details (shows all connected containers and IPs)
docker network inspect app-network

Overlay Networking for Multi-Host (Docker Swarm)

Overlay networks span multiple Docker hosts, enabling containers on different machines to communicate as if they were on the same local network:

# Initialize Docker Swarm
docker swarm init --advertise-addr 192.168.1.10
 
# Create an overlay network
docker network create \
  --driver overlay \
  --attachable \
  prod-network
 
# Deploy a service on the overlay network
docker service create \
  --name api \
  --network prod-network \
  --replicas 3 \
  myapp:1.0

Overlay networking uses VXLAN encapsulation to tunnel traffic between hosts. In Kubernetes, this concept maps to Container Network Interface (CNI) plugins like Flannel, Calico, and Cilium.

Macvlan Networking

Macvlan assigns a real MAC address to each container, making it appear as a physical device on the network:

docker network create \
  --driver macvlan \
  --subnet 192.168.1.0/24 \
  --gateway 192.168.1.1 \
  --opt parent=eth0 \
  macvlan-net
 
docker run -d \
  --network macvlan-net \
  --ip 192.168.1.50 \
  --name legacy-app \
  legacy-image:1.0

Use macvlan when legacy applications require a specific IP on the physical network (hardware licensing, firewall rules).

Debugging Network Issues

# Check container IP and network config
docker inspect container_name | grep -A 20 '"Networks"'
 
# Test connectivity between containers
docker exec api ping -c 3 db
docker exec api curl -v http://db:5432
 
# Check which ports are exposed
docker port container_name
 
# Capture network traffic (requires --network host and tcpdump)
docker run --rm --network container:api \
  -v $(pwd):/captures \
  nicolaka/netshoot \
  tcpdump -w /captures/traffic.pcap
 
# Use netshoot for comprehensive network debugging
docker run --rm -it --network container:api nicolaka/netshoot

Common Mistakes

  • Relying on the default bridge network for container-to-container communication — DNS resolution does not work on the default bridge
  • Binding ports to 0.0.0.0 (all interfaces) when only localhost access is needed — exposes services to the network
  • Using --network host for application containers in production — removes isolation and creates port conflict risks
  • Not using internal: true on database networks — databases can potentially make outbound connections
  • Hardcoding container IPs instead of using DNS hostnames — IPs change when containers restart

Best Practices

  • Always create user-defined bridge networks; never rely on the default bridge for service-to-service communication
  • Bind sensitive ports to 127.0.0.1 not 0.0.0.0: -p 127.0.0.1:5432:5432 for databases
  • Use network segmentation (frontend/backend networks) to enforce least-privilege connectivity between services
  • Use nicolaka/netshoot container for live network debugging — it has every tool (curl, dig, tcpdump, nmap)
  • Name your networks descriptively: payment-backend, auth-frontend, not net1, net2

Key Takeaways

  • User-defined bridge networks support DNS resolution by container name; the default bridge network does not
  • Port mapping (-p hostPort:containerPort) publishes ports to the host; EXPOSE in Dockerfile is documentation only
  • Host networking removes network isolation and should only be used for monitoring or performance-critical tools
  • Docker Compose creates a default network for all services automatically, enabling name-based service discovery
  • The internal: true flag on a network blocks outbound internet access — ideal for database networks
  • Overlay networks enable multi-host container communication in Docker Swarm using VXLAN encapsulation
  • Always use nicolaka/netshoot for debugging — it provides traceroute, nmap, dig, and tcpdump in one image
  • Separate your services into frontend and backend networks to enforce security boundaries at the network layer

Advertisement

Sanjeev Sharma

Written by

Sanjeev Sharma

Full Stack Engineer · E-mopro

Related reading