Docker Networking

Every Docker container gets its own network namespace: its own interfaces, IP address, routing table, and port space. Network drivers decide how that namespace connects to other containers, the host, and the outside world. Most of the time you use the default bridge driver without thinking about it, until two containers can't find each other, a port is unexpectedly exposed to the internet, or DNS fails inside a container.

Knowing the handful of drivers and how port publishing works explains nearly every Docker connectivity issue.

TL;DR

Quick Example

Two containers on a user-defined network, talking by name:

The API reaches Postgres at db:5432 over the private network. Only the API's port 3000 is published, and only to localhost; the database isn't reachable from outside at all.

Core Concepts

Network Drivers

Default vs User-Defined Bridges

Docker's built-in bridge network is a legacy default. Containers on it can reach each other only by IP, and --link is deprecated. User-defined bridge networks provide:

Docker Compose creates a user-defined network per project automatically, which is why service names just work there.

Port Publishing

Containers on a bridge are private. -p HOST:CONTAINER makes Docker program NAT rules so traffic to the host port is forwarded to the container:

EXPOSE in a Dockerfile is documentation; it doesn't publish anything.

Container-to-Host and Container-to-Container

Segmenting Services

Put services on separate networks so a compromised frontend can't reach the database directly:

web reaches api; only api reaches db; and db can't make outbound connections at all. It's the single-host counterpart to Kubernetes NetworkPolicies.

Best Practices

Always Use User-Defined Networks

Create a network per application or stack. You get name-based discovery and isolation, and you avoid the quirks of the default bridge.

Publish Minimally, Bind Narrowly

Publish only the ports that must be reachable from outside Docker, and bind development services to 127.0.0.1. Databases and caches usually need no published port at all.

Be Careful With Host Networking

--network host removes network isolation. The container can bind any host port and reach every host-local service. Use it deliberately, for performance-sensitive networking or tools that need the host's view, not to "fix" connectivity.

Mind the MTU on VPNs and Overlays

Encapsulation (VPNs, VXLAN overlays, some cloud networks) reduces the usable MTU. Mismatched MTUs cause mysterious hangs on large responses while small requests work. Set the network's MTU (--opt com.docker.network.driver.mtu=1400) to match.

Common Mistakes

Assuming the Host Firewall Protects Published Ports

On Linux, Docker inserts iptables/nftables rules that route published ports before many host firewall rules (UFW, for example) are evaluated. A container published with -p 5432:5432 on a cloud VM can be reachable from the internet even though ufw says the port is closed.

Use the DOCKER-USER chain or your cloud's security groups for real filtering.

Relying on Container IPs

Container IPs change whenever a container is recreated. Hard-coding 172.18.0.3 works until the next deploy. Use names and aliases.

Expecting the Default Bridge to Resolve Names

Containers started with plain docker run (no --network) land on the default bridge, where name resolution doesn't work. Create a network and attach both containers to it.

FAQ

Why can't my containers resolve each other by name?

They're probably on the default bridge network or on different networks. Create a user-defined network, attach both containers, and use the container name or a --network-alias.

What's the difference between EXPOSE and -p?

EXPOSE documents which ports the image listens on and enables -P to publish them randomly. -p actually creates the port mapping from the host to the container. Without -p, the port is only reachable from other containers on the same network.

How do I reach a service running on my host from a container?

On Docker Desktop (macOS and Windows), use host.docker.internal. On Linux, add --add-host=host.docker.internal:host-gateway (or extra_hosts in Compose), and make sure the host service listens on an interface the bridge can reach, not only 127.0.0.1.

How do I debug container networking?

Run a toolbox container on the same network: docker run --rm -it --network app-net nicolaka/netshoot. Inside, you have dig, curl, tcpdump, ss, and iperf. docker network inspect app-net shows which containers are attached and their addresses.

Related Topics

References