WireGuard collides with Docker networks — how do I stabilize routing?
Question
On my Ubuntu VPS I have a database cluster closed to the outside; the main interface is `ens192`. I set up a WireGuard tunnel for secure access. When the VPN comes up, IP conflicts and routing errors appear between the local Docker networks (`docker0`) and the WireGuard subnets. How do I permanently stabilize network isolation and the routing table at the server level?
Answer
Short answer: the root cause is overlapping RFC1918 ranges. The fix isn’t a runtime trick, it’s a deterministic addressing plan: pin Docker’s pool, narrow WireGuard’s AllowedIPs, make routes persistent.
Short answer
This isn’t a “route bug”, it’s a planning problem: once WireGuard comes up there are two networks sitting in the same 172.x/10.x range, and the kernel can’t decide which one to route. I covered how Docker sets up its own network layer in Docker containerization: installation and basic usage; the clash comes straight out of that auto-assigned bridge network.
Why
-
A clash is inevitable while Docker picks its pool at random. Automatic network assignment chooses a range on your behalf; the day it lands on your WireGuard subnet, the collision happens on its own.
-
A broad
AllowedIPshijacks the default route. Give it0.0.0.0/0and WireGuard pulls the host’s entire traffic into the tunnel, turning “a collision” into “everything flows through the tunnel”.
What to do
-
Pin Docker’s pool. With
default-address-poolsindaemon.json, pin the block Docker auto-assigns networks from to a range that can never collide with the WireGuard subnet. -
Give WireGuard a tight
AllowedIPs. LetAllowedIPsbe only the DB subnet. Narrow it and the kernel only sends DB traffic towg0. -
Make routes persistent. Ad-hoc
ip route addcommands die on reboot. Define routes viasystemd-networkd/netplan so they come back automatically after a restart. -
Keep the DB box inbound-only over
wg0. Don’t NAT the database to the world; let it accept only connections from the tunnel. -
Verify. Use
ip route get <db-ip>to confirm the packet really leaves viawg0, andwg showto confirm the handshake is live. Confirm with these two commands, not by guessing.
Bottom line: stop chasing collisions at runtime; this is a planning problem. I’d write a documented, non-overlapping subnet plan per interface (ens192 / docker0 / wg0). Pin the Docker pool, narrow AllowedIPs, make routes persistent via systemd, reach the DB only through the tunnel. With a clean addressing plan, routing stabilizes on its own.
Related Reading
Comments
Sign in with your GitHub account to join the discussion. Comments are stored in GitHub Discussions.