The container network model & drivers
CNM, bridge, host, macvlan, overlay.
Every container comes up like a fresh laptop on a desk. It has a network card, it has a routing table, and nothing is plugged into the port yet. Docker's name for the wiring scheme is the Container Network Model (CNM), and three pieces of it are worth naming. The sandbox is the container's own private network stack: its interfaces, its routes, and its Domain Name System (DNS) settings, DNS being the service that turns a name like db into an address. All of that is walled off from the host. The endpoint is the cable. The network is the switch that cable plugs into. A driver decides what kind of switch you get, and both reachability and isolation fall straight out of that one choice. Learn the drivers and you have learned the model.
Five drivers are worth knowing. bridge, host, and none all live inside a single machine. overlay and macvlan exist for the jobs one machine cannot cover on its own. Most days you will create a bridge network and forget the rest are there. The other four each answer one narrow question, and the skill is noticing which question you are actually being asked before you reach for an answer.
bridge builds a private virtual network on the host and hands out addresses from a subnet you pick. Traffic heading outward gets Network Address Translation (NAT), the same trick your home router plays on your phone and your TV, so everything shares one address on the way out and you publish ports to let the replies find their way back in. host throws the sandbox away and binds the container straight onto the host's own interfaces. Latency is as low as it gets. There is also no separate address, no isolation, and no -p publishing, because the container's ports are the host's ports. none gives a container a loopback interface and nothing else, which is exactly right for a batch job that grinds through local files and should never phone home. overlay stitches several machines into one flat network, so a Swarm service on node A can reach a service on node B by name. macvlan is the odd one out. It hands the container its own Media Access Control (MAC) address, the hardware-level name a network card answers to, plus its own Internet Protocol (IP) address on the physical local area network (LAN), the wired network your office or lab already runs. To everything else on that wire, the container looks like a separate machine.
Build your own bridge, then look inside it
Leave the default bridge network alone. It survives for backward compatibility and it ships without name resolution on purpose, so two containers sitting on it can only find each other by address, and addresses move. A network you create yourself fixes that. Docker runs a small DNS resolver at 127.0.0.11 for every container on the network, and each one can reach the others by container name. One command sets it up.
$ docker network create --driver bridge --subnet 172.30.0.0/24 appnetc4e5a1b8f7d3902e6a1b4c9d0e2f3a5b7c8d9e0f1a2b3c4d5e6f7a8b9c0d1e2f$ docker network lsNETWORK ID NAME DRIVER SCOPEc4e5a1b8f7d3 appnet bridge local1a2b3c4d5e6f bridge bridge local5f9e8d7c6b4a host host local0a1b2c3d4e5f none null local
docker network inspect is where the model stops being an idea and becomes a record you can read. Attach a container, then ask Docker what it wrote down: the subnet, the gateway, and every connected container with the address it was handed.
$ docker run -d --name api --network appnet nginx:1.277a2c9f4b1e8d5c3a0f6b9d2e4c7a1b8f0e3d6c9a2b5f8e1d4c7a0b3f6e9d2c5a$ docker network inspect appnet[{"Name": "appnet","Id": "c4e5a1b8f7d3902e6a1b4c9d0e2f3a5b7c8d9e0f1a2b3c4d5e6f7a8b9c0d1e2f","Scope": "local","Driver": "bridge","EnableIPv6": false,"IPAM": {"Driver": "default","Config": [{ "Subnet": "172.30.0.0/24", "Gateway": "172.30.0.1" }]},"Containers": {"7a2c9f4b1e8d5c3a0f6b9d2e4c7a1b8f0e3d6c9a2b5f8e1d4c7a0b3f6e9d2c5a": {"Name": "api","MacAddress": "02:42:ac:1e:00:02","IPv4Address": "172.30.0.2/24","IPv6Address": ""}},"Options": {},"Labels": {}}]
Split the tiers across two networks
Here is a shape you will meet over and over. The web tier has to be reachable from outside. The database has to be reachable from web and from nowhere else. Nothing else on the box should be able to open a socket to it at all. Networks give you that without writing a firewall rule. A container can talk only to the networks it is attached to, so park the database alone on a backend network, put web on a frontend network for inbound traffic, then attach web to backend as well. web can now see the database. Anything else living on frontend cannot.
$ docker network create frontend5b1c8a2f9d4e7c0a3b6f1d8e5c2a9b4f7e0d3c6a1b8f5e2d9c4a7b0f3e6d1c8a$ docker network create backend8d3e0c7a4b1f6e9d2c5a8b0f3e6d1c4a7b2f5e8d0c3a6b9f1e4d7c0a3b6f9e2d$ docker run -d --name db --network backend postgres:16a1f2b3c4d5e6f7089a1b2c3d4e5f60718293a4b5c6d7e8f90a1b2c3d4e5f6071$ docker run -d --name web --network frontend -p 8080:3000 myapp:1.0b3c4d5e6f708192a3b4c5d6e7f8091a2b3c4d5e6f7081920314253647586970a$ docker network connect backend web
Drawings lie. Go and test the boundary. A throwaway busybox container on backend should resolve db by name, and the same throwaway on frontend should come back empty-handed.
$ docker run --rm --network backend busybox nslookup dbServer: 127.0.0.11Address: 127.0.0.11:53Name: dbAddress: 172.19.0.2$ docker run --rm --network frontend busybox nslookup dbServer: 127.0.0.11Address: 127.0.0.11:53*** Can't find db: No answer
When a container needs a real address on the LAN
Sometimes a bridge will not do. A legacy service expects to sit on the office subnet with an address of its own, or a monitoring agent has to send from an address the network team already trusts and has no appetite to re-approve. macvlan attaches the container to a physical interface and gives it a genuine address on that subnet. No NAT. No published ports. It turns up on the LAN the way any other box does.
$ docker network create -d macvlan \--subnet=192.168.1.0/24 --gateway=192.168.1.1 \-o parent=eth0 lan3b7e0d9c2a5f8e1d4c7a0b3f6e9d2c5a8b1f4e7d0c3a6b9f2e5d8c1a4b7f0e3d$ docker run -d --network lan --ip 192.168.1.50 --name probe nginx:1.279f2a5c8e1b4d7a0f3c6b9e2d5a8f1c4b7e0d3a6c9b2f5e8d1a4c7b0f3e6d9a2c
From another machine on the same LAN, and this part matters, not from the Docker host itself, the container answers on its own address like any other box on the subnet.
$ curl -s -o /dev/null -w '%{http_code}\n' http://192.168.1.50200
The driver you pick is the isolation you get
CNM hands you sandboxes, endpoints, and networks. The driver decides how they behave. bridge is the single-host default with NAT on the way out. host deletes network isolation. none is total silence. macvlan puts containers on the LAN with real MAC addresses. overlay spans Swarm nodes. Choose for the topology and the threat model in front of you, not for the flag your fingers already know.
Networks you create get the embedded resolver; the legacy default bridge does not behave the same way for service discovery, so reach for docker network create in anything you plan to keep. host networking buys you raw throughput and one less layer to reason about, and it costs you isolation and any clarity about which port belongs to whom. On a shared host, treat host mode as a privilege somebody granted, and write down who granted it.
When two Compose projects land on the same subnet, you get silent overlap. Set default address pools in daemon.json, or pin explicit subnets in the Compose files, and the collision cannot happen. Overlapping routes rarely announce themselves. They surface as timeouts that look random until someone reads the routing table and finds traffic leaving by one path and trying to come back by another.
Before you change networking on a machine that matters, capture docker network ls and a docker network inspect of the networks involved, and note which host you ran them on. Record the attachment you added, which container onto which network, so the one link to undo is written down rather than remembered. That is the difference between a two-minute rollback and an hour spent guessing which of three networks a container picked up its address from. If a teammate cannot replay the change from the ticket alone, including the subnet you chose and the nslookup answers you saw on a healthy system, the runbook is not finished.
Try this
Run this on a lab engine; Docker 24 or newer is fine. Read the sample output first, so you know what a healthy result looks like before you lean on the command somewhere it counts.
$ docker network create --driver bridge appnet$ docker network lsNETWORK ID NAME DRIVER SCOPEc0ffe… appnet bridge local$ docker network inspect appnet --format '{{.Driver}} {{json .IPAM.Config}}'bridge [{"Subnet":"172.18.0.0/16","Gateway":"172.18.0.1"}]# STATUS: user-defined bridge ready for DNS-based discovery
Takeaway
Name the three pieces, sandbox, endpoint, and network, then pick the driver on purpose. bridge for anything that lives on one host. host, none, macvlan, or overlay only when a written requirement drags you there. Create your own networks so names resolve, and treat host mode as raised privilege every single time.
frontend. It runs nslookup db, and db sits only on backend. What comes back?host network driver differ from a bridge network?