Networking is where most container confusion happens. This lesson demystifies how containers talk to each other and the outside world — from virtual bridges to DNS-based service discovery.

1. How Container Networking Works

Each container gets its own network namespace — an isolated copy of the network stack with its own interfaces, IP addresses, routing table, and iptables rules. From the container's perspective, it has a full network environment just like a standalone machine.

Docker creates virtual network infrastructure to connect these isolated namespaces together and to the outside world.

🏗️ Analogy: Virtual LAN

Think of each container as a computer on a virtual LAN. Docker acts as the network administrator — it creates virtual switches (bridges), assigns IP addresses (DHCP), configures routing, and sets up port forwarding (NAT) so the outside world can reach specific containers.

Key components of Docker networking:

  • Network namespace — isolated network stack per container
  • veth pair — virtual ethernet cable connecting container to bridge
  • Bridge — virtual switch connecting containers together
  • iptables/nftables — NAT and port forwarding rules
  • Embedded DNS — name resolution between containers

2. Network Drivers

Docker uses a pluggable driver model. Each driver provides different connectivity and isolation characteristics:

Driver Description Use Case
bridge Virtual switch on single host; containers get private IPs Default for standalone containers; most common for development
host Container shares host's network namespace directly Performance-critical apps; tools that need raw host network access
none No networking; container only has loopback Security-sensitive batch jobs that need no network
overlay Multi-host networking via VXLAN tunnels Docker Swarm / multi-node clusters
macvlan Container gets a real MAC address on the physical network Legacy apps that need to appear as physical devices on the LAN

3. Bridge Network (Default)

When Docker starts, it creates a virtual bridge called docker0. Every container that uses the default bridge network gets:

  1. A veth pair — one end in the container (usually eth0), one end attached to docker0
  2. A private IP address from the bridge's subnet (typically 172.17.0.0/16)
  3. A default route via the bridge to reach the host and internet
HOST docker0 bridge (172.17.0.1) Container A eth0: 172.17.0.2 nginx :80 Container B eth0: 172.17.0.3 redis :6379 veth veth Host eth0 NAT External: host:8080 → :80
⚠️ Default Bridge Limitation

On the default bridge network, containers can communicate by IP address but NOT by container name. There's no automatic DNS resolution. You must use user-defined bridge networks for name-based discovery.

# Inspect the default bridge
docker network inspect bridge

# See the veth pairs on the host
ip link show type veth

# Container's network view
docker exec mycontainer ip addr show eth0

4. Port Mapping

Containers have private IPs that are not reachable from outside the host. To expose a container's service to the outside world, you map a host port to a container port:

# Map host port 8080 → container port 80
docker run -d -p 8080:80 nginx

# Map on specific interface only
docker run -d -p 127.0.0.1:8080:80 nginx

# Map a range
docker run -d -p 8000-8010:8000-8010 myapp

# Let Docker pick a random host port
docker run -d -p 80 nginx
docker port <container>    # shows assigned port
Client :8080 Host iptables DNAT rule 8080 → 172.17.0.2:80 Container 172.17.0.2 nginx :80

Under the hood, -p 8080:80 creates an iptables DNAT rule:

# Docker adds rules like this automatically:
iptables -t nat -A DOCKER -p tcp --dport 8080 \
  -j DNAT --to-destination 172.17.0.2:80
💡 Port Syntax Mnemonic

-p HOST:CONTAINER — "left is the outside (host), right is the inside (container)." Same order as volume mounts (-v host:container).

5. User-Defined Bridge Networks

User-defined bridges are superior to the default bridge in almost every way:

Feature Default Bridge User-Defined Bridge
DNS by container name ❌ No ✅ Yes
Automatic isolation All containers share one bridge Each network is isolated
Connect/disconnect at runtime Must stop container ✅ Hot-plug
Custom subnets --subnet
# Create a user-defined bridge network
docker network create mynet

# Run containers on that network
docker run -d --name web --network mynet nginx
docker run -d --name api --network mynet node-app

# Containers can reach each other BY NAME
docker exec api ping web        # resolves to web's IP!
docker exec api curl http://web  # works!

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

# Disconnect
docker network disconnect mynet existing-container

# Inspect the network
docker network inspect mynet

# Clean up
docker network rm mynet

Multi-container communication on a user-defined network uses Docker's embedded DNS — no hardcoded IPs, no manual /etc/hosts editing, and containers can be replaced (new IP) without breaking connections.

6. Host Network

With --network host, the container shares the host's network namespace directly. There is no network isolation — the container sees all host interfaces, uses the host's IP, and binds directly to host ports.

# Container binds directly to host port 80
docker run -d --network host nginx

# No -p needed — nginx is listening on host:80 directly
curl http://localhost:80

Trade-offs:

  • ✅ No NAT overhead — slightly better network performance
  • ✅ Container can see all host traffic (useful for monitoring tools)
  • ✅ No port mapping complexity
  • ❌ No network isolation — container can bind any port
  • ❌ Port conflicts — two containers can't both use port 80
  • ❌ Only works on Linux (on Mac/Windows, Docker runs in a VM so "host" is the VM, not your machine)
When to Use Host Networking

Performance-critical applications where NAT overhead matters (high-throughput proxies, real-time streaming), or network tools that need to capture/inspect all host traffic (tcpdump, Prometheus node-exporter).

7. None Network

With --network none, the container gets complete network isolation. It only has a loopback interface (lo) — no external connectivity at all.

# Completely isolated container
docker run --network none alpine ip addr show
# Only shows: lo (127.0.0.1)

# No internet, no container-to-container communication
docker run --network none alpine ping 8.8.8.8
# ping: sendto: Network is unreachable

Use cases:

  • Batch processing jobs that process local data and need no network
  • Security-sensitive computation (cryptographic operations, secret generation)
  • Testing application behavior when network is unavailable

8. DNS & Service Discovery

Docker runs an embedded DNS server at 127.0.0.11 inside every container on a user-defined network. This server resolves container names to their current IP addresses.

# Inside a container on a user-defined network:
cat /etc/resolv.conf
# nameserver 127.0.0.11

# Resolution happens by container name
nslookup web
# Address: 172.18.0.2

# Also works with network aliases
docker run -d --name db --network mynet --network-alias database postgres
# Both "db" and "database" resolve to this container

This is the foundation of microservice communication. Services refer to each other by name, not by IP:

# Application code connects to "redis" not "172.18.0.4"
redis.createClient({ host: 'redis', port: 6379 })

# If the redis container is replaced (new IP), the name still resolves
docker stop redis
docker run -d --name redis --network mynet redis:7
# Other containers automatically resolve the new IP
🏗️ Like Internal DNS at a Company

Just as a company's internal DNS lets you reach mail.internal without knowing the server's IP (and it still works when the server is migrated), Docker's embedded DNS lets containers find each other by name regardless of IP changes.

Knowledge Check

You run two containers on the default bridge network. Can container A reach container B by name (e.g., ping containerB)?

  • Yes — all Docker containers can resolve each other by name
  • No — the default bridge does NOT provide DNS resolution by name
  • Yes — but only if you use --link
  • Only if both containers expose ports

What does -p 3000:80 mean?

  • Map container port 3000 to host port 80
  • Map host port 3000 to container port 80
  • Expose port 3000 and port 80 on both host and container
  • Forward traffic from port 80 to port 3000 inside the container

What is the main trade-off of using --network host?

  • Better security but worse performance
  • Better performance but no network isolation
  • Enables multi-host networking but requires Swarm
  • Automatic DNS but no port mapping support

Hands-On Tasks

🔬 Task 1: User-Defined Network Communication

Create a network, run two containers, and verify name resolution:

# 1. Create a user-defined bridge network
docker network create testnet

# 2. Run two containers on that network
docker run -d --name server1 --network testnet alpine sleep 3600
docker run -d --name server2 --network testnet alpine sleep 3600

# 3. Ping by name (should work!)
docker exec server1 ping -c 3 server2

# 4. Verify DNS resolution
docker exec server1 nslookup server2

# 5. Compare with default bridge — this will FAIL:
docker run -d --name server3 alpine sleep 3600
docker run -d --name server4 alpine sleep 3600
docker exec server3 ping -c 1 server4   # fails!

# 6. Clean up
docker stop server1 server2 server3 server4
docker rm server1 server2 server3 server4
docker network rm testnet
🔬 Task 2: Host Network vs Bridge + Port Mapping

Compare the two approaches for exposing nginx:

# Approach A: Bridge with port mapping
docker run -d --name nginx-bridge -p 8080:80 nginx
curl http://localhost:8080    # works via iptables DNAT

# Check — container has its OWN IP:
docker exec nginx-bridge ip addr show eth0

# Approach B: Host network (Linux only)
docker run -d --name nginx-host --network host nginx
curl http://localhost:80      # direct, no NAT

# Check — container uses HOST's network:
docker exec nginx-host ip addr show
# You'll see the host's real interfaces!

# Compare performance (optional, run ab or wrk)
# Host networking has slightly lower latency for high-throughput

# Clean up
docker stop nginx-bridge nginx-host
docker rm nginx-bridge nginx-host
🌍 Not Just Docker

Podman uses CNI (Container Network Interface) or the newer Netavark for networking — same concepts (bridges, port mapping) but different plumbing. Kubernetes uses CNI plugins like Calico, Cilium, or Flannel — every pod gets an IP, and a flat network model means any pod can reach any other pod by IP (no NAT between pods). The concepts you learned here (namespaces, veth pairs, bridges, DNS discovery) apply everywhere.

🏢 Industry: Service Meshes

Service meshes like Istio and Linkerd build on top of container networking. They inject sidecar proxy containers (Envoy) alongside your application containers. These proxies intercept all network traffic to provide mutual TLS, load balancing, retries, circuit breaking, and observability — all without changing your application code. The foundation is the same networking primitives: network namespaces, iptables rules, and DNS-based service discovery.

Key Takeaways

  • Each container has its own network namespace — isolated interfaces, IPs, and routing
  • Bridge is the default — containers get private IPs and connect via a virtual switch
  • Port mapping (-p HOST:CONTAINER) exposes container services externally via iptables DNAT
  • User-defined bridges are better — automatic DNS, better isolation, runtime connect/disconnect
  • Host network = no isolation — container shares host's network stack, good for performance
  • None network = full isolation — only loopback, for security-sensitive workloads
  • Docker's embedded DNS (127.0.0.11) enables service discovery by container name on user-defined networks
  • Never hardcode container IPs — use names and let DNS handle resolution