Docker Networking: Bridge, Host, Overlay and Custom Networks (2026)

Every Docker container gets its own network namespace — its own IP address, routing table and firewall rules. Docker connects containers to the outside world and to each other via network drivers. The bridge driver creates a private virtual network on the host; the host driver shares the host's network stack for maximum performance; overlay networks span multiple Docker hosts for Swarm clustering. Understanding Docker's built-in DNS and network isolation model is essential for building secure, multi-service applications.

Network Drivers

# Docker network drivers:
#
# bridge   — Software bridge on the host. Default for standalone containers.
#            Containers on the same bridge can communicate; isolated from others.
#
# host     — Container shares host's network namespace. No isolation.
#            Best performance; port conflicts with host are possible.
#
# none     — No networking. Container is fully isolated.
#            Use for batch jobs that don't need network access.
#
# overlay  — Multi-host networking for Docker Swarm.
#            Containers on different hosts communicate as if on the same LAN.
#
# macvlan  — Assigns a real MAC address to container.
#            Container appears as a physical device on the network.
#            Use for legacy apps that expect to be directly on the LAN.
#
# ipvlan   — Like macvlan but shares the host's MAC address.
#
# Third-party plugins: Weave, Calico, Flannel (used by Kubernetes)

# List networks
docker network ls
# NETWORK ID     NAME              DRIVER    SCOPE
# abc123         bridge            bridge    local   ← default bridge
# def456         host              host      local
# ghi789         none              null      local
# jkl012         myapp_backend     bridge    local   ← compose-created

docker network inspect bridge

Bridge Networks

# The default bridge network (docker0):
# - All containers attach to it by default (unless --network specified)
# - Containers communicate by IP address only (no DNS by name)
# - Legacy; avoid for new work — use custom bridge networks instead

docker run -d --name web nginx     # Attaches to default bridge
docker run -d --name db postgres   # Also on default bridge
# web can reach db at 172.17.0.3, but NOT by hostname 'db'

# Custom bridge networks (strongly preferred):
# - Built-in DNS: containers reach each other by service/container name
# - Better isolation: only connected containers can communicate
# - Can set subnet, gateway, IP range

# Create a custom bridge network
docker network create mynet
docker network create \
  --driver bridge \
  --subnet 192.168.10.0/24 \
  --gateway 192.168.10.1 \
  --ip-range 192.168.10.128/25 \
  mynet

# Run containers on the custom network
docker run -d --name web --network mynet nginx
docker run -d --name db  --network mynet postgres:16-alpine

# Now 'web' can reach 'db' by hostname:
docker exec web curl http://db:5432   # DNS works!
docker exec web ping db               # Also works

# Connect a running container to an additional network
docker network connect mynet existing-container

# Disconnect
docker network disconnect mynet existing-container

# Remove unused networks
docker network rm mynet
docker network prune

Container DNS

# Docker's embedded DNS server (127.0.0.11) resolves container names
# on custom networks. This is why you use service names in connection strings.

# In docker-compose.yml:
# DATABASE_URL: postgres://user:pass@db:5432/mydb
#                                    ^^
#                                    service name — Docker DNS resolves this

# DNS resolution order in a container:
# 1. /etc/hosts  — static entries (container's own name, host.docker.internal)
# 2. Docker's embedded DNS (127.0.0.11)
# 3. External DNS (from host's /etc/resolv.conf or configured nameservers)

# Verify DNS from inside a container
docker exec mycontainer cat /etc/resolv.conf
# nameserver 127.0.0.11    ← Docker's DNS
# options ndots:0

docker exec mycontainer nslookup db
# Server:    127.0.0.11
# Address 1: 127.0.0.11
# Name:      db
# Address 1: 192.168.10.3  ← container IP

# Network aliases — multiple names for one container
docker run -d --name postgres-primary \
  --network mynet \
  --network-alias db \          # Can also be reached as 'db'
  --network-alias database \    # And as 'database'
  postgres:16-alpine

# host.docker.internal — special hostname to reach the Docker host from a container
# Useful when a container needs to call a service running on your laptop
# Available on Docker Desktop (Mac/Windows) natively
# On Linux: docker run --add-host=host.docker.internal:host-gateway myapp

Host and None Networks

# Host network — container uses the host's network stack directly
# No virtual NIC, no NAT, no port mapping needed
docker run -d --network host nginx
# nginx listens on host port 80 directly — no -p flag needed/possible

# When to use host networking:
# - Maximum throughput (eliminates NAT overhead)
# - Network monitoring tools (tcpdump, network scanners)
# - Apps that need to bind to specific host IPs
# - Performance-critical services (high-frequency trading, video streaming)

# Downsides:
# - No port isolation — container can conflict with host services
# - No container-to-container DNS (they're all on the host network)
# - Linux only (Docker Desktop on Mac/Windows emulates it)

# None network — complete network isolation
docker run -d --network none myapp
# Container has only loopback (127.0.0.1), no external connectivity
# Use for: data processing jobs, security-sensitive workloads, air-gapped tasks

# Check a container's network mode
docker inspect mycontainer --format='{{.HostConfig.NetworkMode}}'

Custom Networks in Compose

# Define networks in compose.yml for precise isolation control

name: myapp

services:
  nginx:
    image: nginx:alpine
    networks:
      - frontend     # Exposed to clients
    ports:
      - "80:80"
      - "443:443"

  web:
    build: .
    networks:
      - frontend     # Nginx can reach web
      - backend      # Web can reach db and redis

  db:
    image: postgres:16-alpine
    networks:
      - backend      # Only web can reach db — nginx cannot

  redis:
    image: redis:7-alpine
    networks:
      - backend

networks:
  frontend:
    driver: bridge
  backend:
    driver: bridge
    internal: true   # No outbound internet access for backend containers

# With this setup:
# nginx ↔ web  ✅ (both on frontend)
# web ↔ db    ✅ (both on backend)
# nginx ↔ db  ❌ (nginx not on backend)
# db → internet ❌ (backend is internal)

# Use an existing external network (e.g., shared with another Compose project)
networks:
  shared-db:
    external: true   # Must already exist: docker network create shared-db

Network Isolation

# Security principle: minimum network access
# Each service should only be on the networks it actually needs.

# Typical three-tier web app isolation:
#
#   Internet → [nginx:frontend] → [web:frontend+backend] → [db:backend]
#                                                       → [redis:backend]
#
# nginx: only on frontend (serves HTTP/HTTPS to clients, proxies to web)
# web:   on both (receives from nginx, queries db/redis)
# db:    only on backend, internal=true (no internet, only web can reach it)
# redis: only on backend, internal=true

# Verify isolation by trying to ping from a service that shouldn't have access:
docker compose exec nginx ping db
# ping: bad address 'db'  ← correct! nginx can't resolve db

# Port exposure on bridge networks:
# By default, containers are NOT accessible from outside Docker
# unless you use -p (port mapping) or ports: in compose.yml
# Even containers on the same bridge can't reach each other's ports
# across networks — the network boundary is enforced

# Restricting which host interface to bind:
ports:
  - "127.0.0.1:5432:5432"   # DB only accessible from localhost, not external IPs
  - "0.0.0.0:80:80"         # All interfaces (default when no IP specified)

Overlay Networks

# Overlay networks span multiple Docker hosts (Docker Swarm mode).
# Containers on different hosts communicate as if on the same LAN.
# Uses VXLAN encapsulation under the hood.

# Initialize Swarm on the manager node
docker swarm init --advertise-addr 192.168.1.10

# Create an overlay network
docker network create \
  --driver overlay \
  --attachable \           # Allow standalone containers (not just Swarm services)
  myoverlay

# Deploy a Swarm service on the overlay network
docker service create \
  --name web \
  --network myoverlay \
  --replicas 3 \
  myapp:latest

# Encrypted overlay (encrypt VXLAN traffic between hosts)
docker network create \
  --driver overlay \
  --opt encrypted \
  secure-overlay

# Ingress network — the default overlay for Swarm routing mesh
# When you publish a port on a Swarm service, the routing mesh
# automatically routes requests to any node to any healthy replica.
docker service create \
  --name nginx \
  --network myoverlay \
  --publish published=80,target=80 \  # Published on ALL swarm nodes
  --replicas 3 \
  nginx

# Any swarm node at port 80 → load balanced to one of 3 nginx replicas

Troubleshooting

# Common networking problems and how to diagnose them

# 1. Container can't reach another container by name
# → Are they on the same CUSTOM network? (Default bridge has no DNS)
docker network inspect mynet | jq '.[0].Containers'

# 2. Can't connect to container from host
# → Is the port mapped? Is it bound to 0.0.0.0?
docker port mycontainer
docker inspect mycontainer --format='{{json .HostConfig.PortBindings}}'

# 3. Container can't reach the internet
# → Is the network internal: true?
# → Is the Docker daemon's DNS working?
docker exec mycontainer curl https://google.com
docker exec mycontainer cat /etc/resolv.conf

# Useful debugging container (has curl, nslookup, ping, netcat)
docker run --rm --network mynet nicolaka/netshoot \
  curl http://web:3000/health

# Or run netshoot attached to an existing container's network namespace:
docker run --rm --network container:mycontainer nicolaka/netshoot \
  ss -tlnp   # Show listening ports inside mycontainer's namespace

# 4. "port is already allocated" error
# → Another process (or container) is using that host port
lsof -i :8080         # Find what's using port 8080 on the host
docker ps --format '{{.Ports}}'  # See all port mappings

# 5. Slow DNS resolution
# → Check /etc/resolv.conf options ndots value
# → Add custom DNS: docker run --dns 8.8.8.8 myapp
# → Or in daemon.json: { "dns": ["8.8.8.8", "8.8.4.4"] }
Next: Phase 6 — Docker Volumes and Storage covers named volumes, bind mounts, tmpfs, volume drivers for cloud storage and patterns for database persistence and backup.