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.
Table of Contents
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.