A Docker network is allocated 172.20.0.0/16, which the corporate VPN also routes. How do you fix the collision?
answer
- Two routes to the same block, one is local
- The more specific, connected route wins
- One file, two keys, one restart
- Existing networks keep what they were given
- Ask which ranges are genuinely unrouted
basics
~20 sMove Docker's addressing off the routed range. Set default-address-pools (and bip for docker0) in /etc/docker/daemon.json to a block nothing else routes, restart the daemon, then delete and recreate existing networks — pools apply only when a subnet is first allocated.
solid answer
~40 sThe symptom is that anything on the VPN's 172.20.0.0/16 becomes unreachable from the host and its containers, because the local route for the Docker bridge is at least as specific as the VPN route and wins. Fixing one network is easy: recreate it with `docker network create --subnet 10.42.7.0/24`, or pin the subnet in the file that defines it. The real fix is host-wide: in `/etc/docker/daemon.json` set `default-address-pools` to bases the enterprise does not route, with a `size` that says how large each carved network is, and set `bip` if docker0 itself sits in the way. Restart the daemon. Existing networks keep the subnet they were given, so remove and recreate them, then confirm with `docker network inspect`.
code
json · 7 lines{
"bip": "10.202.0.1/24",
"default-address-pools": [
{ "base": "10.203.0.0/16", "size": 24 },
{ "base": "10.204.0.0/16", "size": 24 }
]
}go deeper
Recognise the shape of the problem: a service you can normally reach over the VPN stops working once Docker is running, because Docker has taken that address range for itself on your machine.
Explain the routing mechanism — a connected route to the bridge beating the VPN's route — and name the two daemon.json keys, bip for docker0 and default-address-pools for everything Docker creates afterwards.
Demonstrate the full remediation including the step people skip: restart the daemon, then delete and recreate the networks that already hold colliding subnets, and verify with docker network inspect rather than assuming.
Own the fleet decision: an allocation agreed with the network team, expressed as pools in configuration management so every host is addressed identically, with the range chosen against everything the organisation routes rather than the next block along.
### Why a collision breaks things Docker's allocator picks from a list of private ranges that a great many enterprises also use — 172.16.0.0/12 in particular is extremely common inside corporate networks and VPNs. When the daemon hands a network 172.20.0.0/16, it also installs a route on the host sending that whole block to the bridge interface. Now the host has two ways to reach 172.20.x.x: the VPN's route and the local bridge route. The local, directly-connected route wins, so packets aimed at a colleague's internal service on 172.20.4.9 go to the bridge, find nothing, and time out. From inside a container it is the same story from one hop further in. The reason this bites laptops especially hard is timing. The allocator does check the host routing table before it takes a candidate pool, so if the VPN is already up when the network is created, Docker usually skips the range. But developers create networks with the VPN down and connect later, and the network keeps the subnet it was given — allocation is a one-time decision, not something re-evaluated when routes change. ### Fixing one network If only one network is in the way, give it an address block explicitly instead of letting the allocator choose: ``` docker network rm checkout-net docker network create --subnet 10.42.7.0/24 --gateway 10.42.7.1 checkout-net ``` Where the network is created by a file rather than by hand, put the same subnet in that file's IPAM section so it is reproduced on every machine. The important part is that the choice is recorded somewhere, not typed once. ### Fixing the host One network at a time does not scale, and it does not help the *next* network, which the allocator will happily place back in the same neighbourhood. The host-wide control is `/etc/docker/daemon.json`: ``` { "bip": "10.202.0.1/24", "default-address-pools": [ { "base": "10.203.0.0/16", "size": 24 }, { "base": "10.204.0.0/16", "size": 24 } ] } ``` `default-address-pools` replaces the built-in candidate list. Each entry gives a `base` block and a `size`, and `size` is the prefix length of each network carved from that base — so a /16 base at size 24 yields 256 networks of 254 usable addresses each. `bip` (bridge IP) is separate: it sets the address and subnet of `docker0` itself, written as the gateway address with a prefix, and it is the reliable way to move the default bridge specifically. Restart the daemon after editing (`systemctl restart docker`); on Docker Desktop the same JSON is edited through the engine settings pane and the engine restarts itself. Validate the JSON before restarting. A syntax error in `daemon.json` stops the daemon from coming back up, which turns a routing annoyance into an outage. ### The step people forget Changing the pools changes nothing that already exists. Subnets are assigned when a network is created and are immutable afterwards, so every network already sitting on 172.20.0.0/16 stays there until you remove and recreate it: ``` docker network ls --filter driver=bridge docker network rm <name> # after disconnecting or stopping its containers ``` Then recreate them and verify: ``` docker network inspect <name> -f '{{range .IPAM.Config}}{{.Subnet}}{{end}}' ``` A candidate who edits the config, restarts, and declares victory without re-creating the networks has not fixed the machine. ### Choosing the replacement range Do not simply move one square along and hope. Ask the network team which blocks are genuinely unrouted — in a large enterprise most of 10.0.0.0/8 is spoken for too, and the answer is usually a specific allocation reserved for container hosts. Two properties matter: nothing on any VPN, peering link or partner network routes it, and it is big enough that the size you choose leaves plenty of networks. Because container ranges are host-local and NAT'd on the way out, the same block can be reused identically on every host, which is what makes it practical to bake the file into configuration management and have every machine addressed the same way. ### Distinguishing the failure One diagnostic sentence separates this from every other networking complaint: the destination that fails is *inside a range Docker owns locally*. `ip route get 172.20.4.9` on the host naming a bridge interface rather than the VPN interface proves it in one command, and no amount of DNS or firewall investigation would have found it.
- You changed default-address-pools and restarted the daemon, but the existing networks are still on the old subnets. Why?Because a network's subnet is assigned once, when the network is created, and is immutable afterwards. The pool list only governs future allocations. Remove and recreate every network still sitting in the colliding range — which means disconnecting or stopping the containers attached to them first — and then confirm the new subnet with `docker network inspect`.
- You set default-address-pools, but docker0 is still on 172.17.0.0/16. What did you miss?The default bridge has its own key. `bip` sets docker0's address and subnet directly, written as the gateway address with a prefix, for example `"bip": "10.202.0.1/24"`. Set it alongside the pools and restart the daemon. If containers are attached to the default bridge, they must be stopped for the bridge to be reconfigured cleanly.
- Why doesn't Docker just avoid the VPN's range automatically?It tries — the allocator skips candidate pools that overlap routes already present in the host's routing table. But that check runs only at allocation time. Create the network with the VPN down, connect afterwards, and the network keeps the subnet it was given; nothing re-evaluates it. That timing is exactly why laptops hit this and always-connected servers usually do not.
- What does `size` mean in a default-address-pools entry?It is the prefix length of each network carved out of that entry's `base` block, not the size of the base itself. `{"base": "10.203.0.0/16", "size": 24}` yields 256 separate /24 networks, each with 254 usable addresses. Choosing a larger size number gives you more networks with fewer addresses each; choosing a smaller one does the reverse.
saying these in an interview costs you the question
- Says the only fix is disconnecting the VPN
- Edits daemon.json but never restarts the daemon
- Assumes changing pools re-addresses existing networks
- Thinks --subnet on one network fixes the whole host
- Confuses bip with default-address-pools
- Picks a replacement range without checking what is routed