Each container gets its own network namespace (lesson 4), which means its own interfaces, routing table and firewall rules. Docker’s networking layer is what connects those isolated stacks to each other and to the outside world.
Most container networking confusion comes down to one fact that nobody tells beginners: the default bridge network behaves differently from every network you create yourself.
Topic 1: The Container Network Model
Docker networking follows a specification called the Container Network Model (CNM), implemented by an open-source library called libnetwork. Three concepts:
| Concept | What it is |
|---|---|
| Sandbox | A container’s whole network stack: interfaces, routing table, DNS settings |
| Endpoint | A network interface that joins a sandbox to a network — one veth end |
| Network | A collection of endpoints with connectivity between them |
A container can have several endpoints and therefore sit on several networks at once. That is how you build a service that talks to both a public-facing network and a private database network without the two being connected.
libnetwork exposes two pluggable interfaces: network drivers (how connectivity is provided) and IPAM drivers (how addresses are allocated). Third-party drivers plug in at the same points — this is the extensibility story, and it is why Weave, Calico and cloud-native plugins can exist.
Topic 2: The Native Drivers
docker network ls
# NETWORK ID NAME DRIVER SCOPE
# a1b2c3d4e5f6 bridge bridge local
# f6e5d4c3b2a1 host host local
# 0011223344ff none null local
| Driver | What it does | Use it when |
|---|---|---|
bridge | The default. A private virtual network on one host, NAT to the outside | Almost always, on a single host |
host | The container shares the host’s network stack entirely | Performance-critical or port-scanning tooling |
none | No interfaces at all except loopback | Batch jobs that must not reach the network |
overlay | A virtual network spanning multiple hosts | Swarm services, multi-host clusters |
macvlan | Gives the container its own MAC address on the physical LAN | Legacy apps that need to look like a physical host |
ipvlan | Like macvlan, sharing the host’s MAC | Environments where the switch limits MACs per port |
host deserves a specific warning: there is no port mapping and no isolation. A container on the host network binding port 80 is the host binding port 80, and it will collide with anything else that wants it.
macvlan is the answer when a container must appear on the physical network with its own IP — some appliances and licence servers genuinely require this — and it needs the physical switch to permit promiscuous mode, which is often where the plan dies.
Topic 3: What Installing Docker Did to Your Host
Before Docker, ip addr on a plain host shows lo and eth0. After installing Docker, there is a new interface:
ip addr show docker0
# 3: docker0: <BROADCAST,MULTICAST,UP> mtu 1500
# inet 172.17.0.1/16 brd 172.17.255.255 scope global docker0
docker0 is a software bridge — a virtual switch. When you start a container:
- Docker creates a veth pair — a virtual cable with two ends.
- One end goes into the container’s namespace and is renamed
eth0. - The other end stays on the host (you will see it as something like
vethf5e485a) and is plugged intodocker0. - The container gets an IP from the bridge’s subnet (
172.17.0.0/16by default) and a MAC address.
docker run -d --name web nginx:alpine
docker inspect web --format '{{range .NetworkSettings.Networks}}{{.IPAddress}}{{end}}'
ip addr | grep veth # the host end of the cable
Outbound traffic from the container is masqueraded (SNAT) to the host’s IP, which is why containers can reach the internet without any configuration. Inbound traffic reaches nothing until you publish a port.
Topic 4: The Default Bridge Has No DNS — and That Is the Whole Lesson
Start two containers with no --network flag and they land on the default bridge:
docker run -d --name dc1 alpine sleep 1d
docker run -d --name dc2 alpine sleep 1d
docker inspect dc2 --format '{{range .NetworkSettings.Networks}}{{.IPAddress}}{{end}}'
# 172.17.0.4
docker exec dc1 ping -c 2 172.17.0.4 # works
docker exec dc1 ping -c 2 dc2 # FAILS: bad address 'dc2'
IP works. Names do not. Now create your own bridge:
docker network create --driver bridge --subnet 10.10.0.0/24 mybridge
docker run -d --name c1 --network mybridge alpine sleep 1d
docker run -d --name c2 --network mybridge alpine sleep 1d
docker exec c1 ping -c 2 c2 # works — resolved by name
User-defined bridge networks have automatic DNS resolution by container name. The default bridge does not. Docker runs an embedded DNS resolver at 127.0.0.11 inside each container on a user-defined network, and it resolves container names and network aliases.
This is not a minor convenience. Container IPs change on every recreate, so anything hard-coding an IP is broken by design. Name resolution is what makes DATABASE_HOST=db work — and it only works if you created the network yourself. Docker Compose (lesson 11) creates a user-defined network for every project automatically, which is why this problem is invisible to people who start with Compose.
User-defined networks also give you better isolation (containers on different user-defined networks cannot reach each other) and live attach/detach:
docker network create backend
docker network connect backend api # api is now on two networks
docker network disconnect bridge api
docker network inspect mybridge # lists subnet, gateway and every attached container
Topic 5: Publishing Ports, and What Actually Happens
docker run -d -p 8080:80 --name web nginx:alpine
Under the hood Docker inserts a DNAT rule into the host’s iptables nat table:
sudo iptables -t nat -L DOCKER -n
# DNAT tcp -- 0.0.0.0/0 0.0.0.0/0 tcp dpt:8080 to:172.17.0.2:80
Traffic arriving on host port 8080 is rewritten to the container’s IP on port 80 and forwarded across the bridge. That is the entire mechanism.
Two operational consequences that surprise people:
Docker’s rules can bypass your firewall. Docker writes into the DOCKER chain, which is traversed before many INPUT-based firewall rules. A ufw-blocked port can still be reachable if a container published it. Bind to a specific interface when you do not want the world reaching it:
docker run -d -p 127.0.0.1:8080:80 nginx:alpine # localhost only
Containers on the same network do not need publishing at all. If api and db are on backend, api reaches db:5432 directly. Publishing 5432 to the host as well means you have also exposed your database to everything that can reach the host. Publish only what genuinely needs to come in from outside.
docker port web # 80/tcp -> 0.0.0.0:8080
Topic 6: Beyond One Host
Everything above stops at the edge of one machine. Containers on host A cannot reach containers on host B by container name, and docker0 is local to each host.
The overlay driver solves this: it builds a virtual layer-2 network across multiple hosts using VXLAN encapsulation, so containers on different machines share one address space and one DNS namespace. Overlay networks are created and managed by Docker Swarm, Docker’s own clustering mode.
Swarm is worth knowing about even though few new deployments choose it:
- A manager node accepts your desired state (a service) and dispatches units of work (tasks) to workers. Multiple managers are supported; one is the leader.
- A worker node runs the tasks and reports status back.
docker swarm initanddocker swarm joinset it up;docker service createruns a replicated service across the cluster.
Once you are running containers on multiple hosts, the requirements change shape entirely: you need to survive node failure, scale services, do zero-downtime deployments, and keep it all simple enough to operate. That is the orchestration problem, and it is where Kubernetes — the subject of its own path on this site — took over from Swarm in practice.
Try it yourself: Create two user-defined networks, frontend and backend. Put an nginx container on frontend, a Postgres on backend, and an API container on both. Prove that nginx cannot reach Postgres, but the API can reach both. That three-container layout is the smallest realistic example of network segmentation, and it maps almost exactly onto what a NetworkPolicy does in Kubernetes.
Common mistake: Hard-coding a container’s IP address in configuration. Container IPs are assigned from a pool at start time and change whenever the container is recreated. Put containers on a user-defined network and use container names — that is what the embedded DNS is for.