Networking is where containers feel different from regular processes — and where most beginner confusion happens. This lesson clears it up.
Network Drivers
| Driver | What it does | When to use |
|---|---|---|
| bridge | Default. Containers get a private subnet on the host and reach the outside via NAT. | Single-host workloads, local development. |
| host | Container shares the host's network namespace. No isolation, no port mapping. | Maximum performance, Linux only, when port collisions are not an issue. |
| none | No network at all. | Batch jobs that should never call out. |
| overlay | Multi-host networking using VXLAN. | Docker Swarm; rare outside it. Kubernetes uses CNI plugins instead. |
| macvlan | Container gets its own MAC address on the physical network. | Legacy apps that need to look like a real host. |
The Default Bridge
Out of the box, every container attaches to a bridge called bridge. Each container gets a private IP (e.g., 172.17.0.x). This works, but the default bridge has a quirk: containers on it can only reach each other by IP, not by name. So everyone uses user-defined bridge networks instead, which provide automatic DNS-based service discovery.
docker network create app-net
docker run -d --name db --network app-net postgres:16
docker run -d --name api --network app-net \
-e DATABASE_URL=postgres://postgres@db:5432/postgres \
myapi:1.0
The API resolves the hostname db to the database container's IP automatically. No /etc/hosts editing, no environment variables full of IP addresses.
Publishing Ports
A container's listening ports are private to its network. To accept traffic from the host or the outside world, you publish a port:
docker run -p 8080:80 nginx # host:8080 → container:80
docker run -p 127.0.0.1:8080:80 nginx # localhost only
docker run -p 8080:80/tcp -p 8080:80/udp ... # both protocols
docker run -P nginx # publish all EXPOSEd ports to random hosts
Inspect what's published:
docker port web
# 80/tcp → 0.0.0.0:8080
# 80/tcp → :::8080
Container-to-Container Communication
The general rule: containers on the same user-defined network reach each other by container name; from outside, you reach a container only via published ports.
Internet
│
▼ (port 8080 published)
┌────────────┐
│ Host │
│ ┌──────┐ │
│ │ web │◀─┼── api network ──▶┌──────┐
│ │ :80 │ │ │ api │
│ └──────┘ │ │:3000 │
│ │ └──┬───┘
│ │ api network │
│ │ ▼
│ │ ┌──────┐
│ │ │ db │
│ │ │:5432 │
│ │ └──────┘
└────────────┘
Reaching the Host From Inside a Container
Sometimes a containerised app needs to reach a service running on the host itself (e.g., a database installed natively while you containerise the app). Use host.docker.internal on Mac/Windows; on Linux you can add a host alias:
docker run --add-host=host.docker.internal:host-gateway myapp
Inspecting Networks
docker network ls # list networks
docker network inspect app-net # see attached containers + IPs
docker network connect app-net some-ctr # attach an existing container
docker network disconnect app-net some-ctr
docker network rm app-net # remove (must have no containers attached)
Common Pitfalls
- "Connection refused" between containers — usually means they are on different networks. Put them on the same user-defined network.
- Using
localhostfrom inside a container to reach another container —localhostis the container itself. Use the other container's name. - Port conflict on the host — only one process can listen on a host port. Pick another with
-p 8081:80. - App binds to
127.0.0.1inside the container — then no external traffic can reach it. Bind to0.0.0.0.
From Docker to Kubernetes
In Kubernetes, every pod gets its own network namespace and IP — containers within a pod share the namespace and reach each other via localhost. Pods on different hosts communicate via a CNI (Container Network Interface) plugin like Calico, Cilium, or AWS VPC CNI. The fundamentals you've just learned (DNS, ports, isolation) apply directly.