Initializing Enclave...

How to Fix Docker0 Bridge IP Conflict with Host Network Range

Threat/Impact Level: HIGH | Exploitability/Downtime Risk: HIGH | Time to Fix: 10 mins

TL;DR

  • What broke: Docker auto-assigned 172.17.0.0/16 to the docker0 bridge, which overlaps with your host's LAN, VPN, or corporate subnet — packets destined for real hosts are black-holed or misrouted into containers.
  • How to fix it: Override the default bridge CIDR in /etc/docker/daemon.json with a non-conflicting range and restart the daemon.
  • Action: Drop your daemon.json or ip route output into the Client-Side Sandbox above to auto-generate a safe, non-overlapping CIDR assignment.

The Incident (What Does the Error Mean?)

You'll typically see this surface as silent connectivity failure — no clean error, just unreachable hosts. Diagnostics that expose it:

$ ip route
default via 10.0.0.1 dev eth0
172.17.0.0/16 dev docker0 proto kernel scope link src 172.17.0.1
172.17.0.0/16 dev eth0 proto kernel scope link src 172.17.42.5

$ ping 172.17.0.50
# Packet goes to a container instead of the intended corporate host
Request timeout for icmp_seq 0

The kernel routing table now has two competing routes for the same prefix. The docker0 interface wins based on metric, and traffic meant for 172.17.x.x corporate hosts gets swallowed by the Docker bridge. No error. No log. Just a black hole.


The Attack Vector / Blast Radius

This isn't just a connectivity nuisance — the blast radius is significant:

1. VPN Tunnel Poisoning: If your corporate VPN pushes a 172.17.0.0/16 route, Docker's bridge route takes precedence at the kernel level. All VPN traffic to that range is silently rerouted to local containers. Credentials, internal API calls, database connections — all going nowhere, or worse, to a container process.

2. Lateral Movement Surface: In multi-tenant or shared-host environments, a malicious container on the same bridge can intercept ARP traffic for the conflicting range. This is a viable ARP spoofing vector against the host's own network stack.

3. Cascading Service Failure: Microservices calling internal endpoints in the 172.17.x.x range will fail with connection timeouts. Health checks pass (container is up), but inter-service calls die. This is the class of failure that burns hours in a production incident because nothing is technically down.

4. Split-Brain DNS/Service Discovery: Consul, Kubernetes CoreDNS, or internal service registries resolving to 172.17.x.x addresses will route to Docker bridge IPs, not the registered services.


How to Fix It (The Solution)

Basic Fix — Override the docker0 Bridge CIDR

Edit or create /etc/docker/daemon.json:

- {}
+ {
+   "bip": "192.168.200.1/24",
+   "fixed-cidr": "192.168.200.0/24"
+ }

Choose a CIDR that does not appear in:

ip route show
cat /etc/resolv.conf
vpn-cli show routes   # or equivalent for your VPN client

Apply the change:

sudo systemctl restart docker
ip addr show docker0
# Verify: docker0 should now show 192.168.200.1/24

Enterprise Best Practice — Centralized Daemon Config with Address Pools

For fleets managed via Ansible, Chef, or cloud-init, enforce a global address pool policy that avoids all known corporate ranges:

- {
-   "bip": "172.17.0.1/16"
- }
+ {
+   "bip": "192.168.200.1/24",
+   "default-address-pools": [
+     { "base": "192.168.200.0/16", "size": 24 }
+   ],
+   "fixed-cidr": "192.168.200.0/24",
+   "log-driver": "json-file",
+   "log-opts": { "max-size": "10m", "max-file": "3" }
+ }

Why default-address-pools matters: Without it, docker network create for user-defined bridge networks will still auto-assign from the 172.16.0.0/12 space — recreating the conflict for any non-default network. The default-address-pools key controls that allocation globally.

Verify no overlap remains:

docker network inspect bridge | grep -A5 'IPAM'
ip route | grep docker
# Zero overlapping prefixes with: ip route | grep -v docker

💡 Tired of pasting proprietary configs into ChatGPT? Generic AI tools log your company's ARNs, DB strings, and private keys. StackEngine is a zero-backend, pure Client-Side WASM utility. Drop your failing config into the sandbox above. We redact your secrets locally in the browser and auto-generate the refactored code using your own API key.


Prevention in CI/CD

1. Checkov — Static Analysis on daemon.json:

checkov -f /etc/docker/daemon.json --check CKV_DOCKER_2

Checkov's CKV_DOCKER_2 flags default bridge usage. Add this to your golden image pipeline.

2. OPA/Conftest Policy for daemon.json:

package docker.daemon

deny[msg] {
  input.bip == "172.17.0.1/16"
  msg := "docker0 bridge uses default CIDR 172.17.0.0/16 — conflicts with standard corporate ranges. Override bip in daemon.json."
}

deny[msg] {
  not input["default-address-pools"]
  msg := "default-address-pools not defined. Docker will auto-assign from 172.16.0.0/12 for user-defined networks."
}

Run in CI: conftest test /etc/docker/daemon.json --policy docker_policy.rego

3. Terraform (for EC2/VM provisioning):

- user_data = "apt-get install -y docker.io"
+ user_data = <<-EOF
+   apt-get install -y docker.io
+   cat > /etc/docker/daemon.json <<'JSON'
+   {
+     "bip": "192.168.200.1/24",
+     "default-address-pools": [{"base": "192.168.200.0/16", "size": 24}]
+   }
+   JSON
+   systemctl restart docker
+ EOF

4. Network Range Audit Script (run pre-deploy):

#!/bin/bash
CONFLICTS=$(ip route | grep -v docker | awk '{print $1}' | while read route; do
  python3 -c "
import ipaddress
try:
  a = ipaddress.ip_network('$route', strict=False)
  b = ipaddress.ip_network('192.168.200.0/24', strict=False)
  if a.overlaps(b): print('CONFLICT: $route overlaps proposed docker0 CIDR')
except: pass
"
done)
[ -n "$CONFLICTS" ] && echo "$CONFLICTS" && exit 1
echo "No conflicts detected. Safe to apply daemon.json."

Embed this in your pre-flight Ansible playbook or Packer build step.

Related Diagnostics

"Part of the Security Utility Matrix."

View all 140 Security Tools →