Docker & Containerization

Container Networking

18 min Lesson 7 of 30

Container Networking

Networking is where most Docker surprises happen in production. A container that works perfectly in isolation silently fails when it tries to reach a database on the same host — or a service in another container — because the developer did not understand how Docker has isolated the network stack. This lesson closes that gap: you will understand every network driver Docker ships with, how port publishing works at the kernel level, and how container DNS lets services find each other by name instead of IP.

How Docker Isolates Networking

Each container gets its own network namespace — a Linux kernel primitive that gives the container a private view of network interfaces, routing tables, and iptables rules. From inside the container, eth0 is its only interface and it has no knowledge of the host's physical NIC or other containers, unless you explicitly connect them. Docker wires namespaces together using one of several network drivers.

The Three Core Network Drivers

bridge (default)

The default driver. Docker creates a virtual Ethernet bridge (docker0) on the host. Each container connected to a bridge network gets a veth pair: one end inside the container namespace, the other end attached to the bridge. Containers on the same bridge can reach each other by IP. Traffic leaving the bridge is NAT-ed by iptables through the host's physical interface.

When you create a user-defined bridge (any bridge other than docker0), Docker also enables an embedded DNS server — containers can resolve each other by container name. The default docker0 bridge does not have this DNS feature, which is why the docs tell you never to rely on --link in production.

Key distinction: The built-in docker0 bridge gives containers IPs but no DNS. A user-defined bridge network adds container-name DNS resolution automatically. Always create a named network for multi-container apps — it is the single most important Docker networking habit.

host

With --network host, the container shares the host's network namespace entirely. There is no isolation: eth0 inside the container is literally the host's eth0. No NAT, no port publishing needed. This gives the lowest possible latency (no veth hop, no iptables translation) and is used by performance-critical applications — high-frequency trading, eBPF-based network agents, sidecar proxies like Envoy in some configurations.

Production pitfall: On Linux, --network host works as described. On Docker Desktop for macOS and Windows, the container still runs inside a Linux VM, so --network host gives you the VM's network, not your laptop's. Code that works perfectly on a Linux CI runner may behave differently on a Mac developer machine.

none

The container gets its own namespace but no interfaces are configured — only a loopback (lo). Used for batch jobs that process data from mounted volumes with no network I/O at all, or as a hardened security posture for containers that must not exfiltrate data. You can always attach additional interfaces after the fact with docker network connect.

Port Publishing

A bridge container's IP is private to the host — nothing outside can reach it directly. Port publishing maps a host port to a container port via iptables DNAT rules that Docker manages for you.

# Map host port 8080 to container port 80 docker run -d -p 8080:80 nginx # Bind only to a specific host interface (avoid 0.0.0.0 in prod) docker run -d -p 127.0.0.1:8080:80 nginx # Publish without specifying host port — Docker picks an ephemeral port docker run -d -p 80 nginx docker port <container_id> # see which port was assigned # UDP port (default is TCP) docker run -d -p 5353:5353/udp my-dns-server

When you publish 0.0.0.0:8080->80, Docker inserts an iptables PREROUTING DNAT rule so that any packet arriving on the host's port 8080 is redirected to the container's private IP on port 80. This means the container is reachable from the internet if the host's firewall allows port 8080 — a common cause of unintended exposure. Prefer binding to 127.0.0.1 and using a reverse proxy (Nginx, Caddy) in front, which terminates TLS and controls access.

Production practice: Never publish ports directly to 0.0.0.0 for databases (Postgres, Redis, MongoDB). Bind them to 127.0.0.1 or, better, do not publish them at all — containers on the same user-defined network can reach each other without any published port.

Container DNS on User-Defined Bridge Networks

When containers are on the same user-defined network, Docker's embedded DNS resolver (127.0.0.11) answers queries using container names, service aliases, and network aliases as hostnames. The resolver is injected into each container's /etc/resolv.conf automatically.

# Create an isolated network docker network create app-net # Start a Postgres container — no published port needed docker run -d --name db --network app-net \ -e POSTGRES_PASSWORD=secret postgres:16-alpine # Start an app container on the same network docker run -d --name api --network app-net \ -e DATABASE_URL=postgres://postgres:secret@db:5432/mydb \ my-api-image # From inside 'api', 'db' resolves to the Postgres container's IP docker exec api nslookup db # Server: 127.0.0.11 # Address: 127.0.0.11:53 # Name: db

This is exactly how Docker Compose wires services together — it creates a shared network and each service is reachable by its service name. Understanding this makes Compose configs far less magical.

Diagram: Bridge Networking

Docker bridge network — two containers communicating via docker0 bridge with NAT to the internet Linux Host eth0 (host NIC) Internet / External Traffic iptables NAT docker0 bridge (172.17.0.1) veth veth Container A (api) eth0: 172.17.0.2 DNS: 127.0.0.11 Published: 0.0.0.0:8080 → 80 Container B (db) eth0: 172.17.0.3 DNS: 127.0.0.11 No published port db:5432 outbound
Docker bridge network: containers share a virtual bridge, reach each other by IP (or by name on a user-defined network), and reach the internet via iptables NAT on the host NIC.

Inspecting and Debugging Networks

# List all networks docker network ls # Inspect a network — see connected containers and their IPs docker network inspect app-net # Attach a running container to an additional network docker network connect app-net existing-container # Disconnect docker network disconnect app-net existing-container # Run a throwaway debug container on the same network docker run --rm --network app-net nicolaka/netshoot \ nmap -p 5432 db # Check iptables rules Docker created (run as root) iptables -t nat -L -n --line-numbers | grep DOCKER

Multi-Host Networking: Overlay Networks

Bridge and host networks are single-host constructs. When containers need to communicate across multiple hosts (a Swarm cluster or as a building block before you move to Kubernetes), Docker provides the overlay driver, which creates a distributed virtual network using VXLAN tunnels. Overlay is out of scope for this lesson but worth knowing it exists — the same mental model (user-defined network + DNS by service name) applies, just stretched across hosts.

Production Checklist

  • Always use user-defined bridge networks — never rely on the default docker0 bridge or --link.
  • Do not publish database ports to 0.0.0.0 — keep them internal to the network; only the app/proxy container needs a published port.
  • Bind reverse-proxy ports explicitly — use 127.0.0.1:80:80 if a host-level proxy (Nginx) is handling TLS termination.
  • Use container names as hostnames in connection strings; hard-coded IPs break every time a container is recreated.
  • Segment networks by trust — a frontend container should not share a network with the database; put a middle-tier API between them.
Google SRE practice: Treat container networks as firewall zones. At Google scale, every service-to-service connection is explicitly allowed; everything else is denied. Model the same principle locally: create one network per tier (frontend, backend, data) and only bridge containers to the networks they legitimately need. This habit maps directly to Kubernetes NetworkPolicy rules when you graduate to k8s.