Docker’s default docker0 bridge uses 172.17.0.0/16, and user-defined networks (including those created by docker-compose stacks deployed from aaPanel’s Docker manager) are allocated sequentially from 172.18.0.0/16 upward through 172.31.0.0/16, then 192.168.0.0/20 blocks. When the host, VPN, or LAN uses an overlapping range, the kernel installs a directly connected route for the bridge that takes precedence over or collides with the existing route, causing lost SSH sessions, unreachable internal hosts, or containers unable to reach LAN resources. Docker does not check the host’s existing routes when it picks a subnet for a new network, and aaPanel’s Docker manager passes through Docker’s defaults without exposing an address-pool setting.
Quick Fix
Confirm the conflict, then redefine Docker’s address pools:
ip -4 route show | sort
docker network ls -q | xargs docker network inspect -f '{{.Name}} {{range .IPAM.Config}}{{.Subnet}}{{end}}'
Set a non-conflicting default pool and bridge IP in /etc/docker/daemon.json, then restart:
cat > /etc/docker/daemon.json <<'EOF'
{
"bip": "10.200.0.1/24",
"default-address-pools": [
{ "base": "10.201.0.0/16", "size": 24 }
]
}
EOF
systemctl restart docker
Note: this only affects networks created after the restart. Existing networks must be recreated (see Step 4).
Step-by-Step Resolution
1. Identify the overlapping subnet
List host routes and Docker networks and compare them:
ip -4 addr show
ip -4 route show
docker network inspect $(docker network ls -q) --format '{{.Name}}: {{range .IPAM.Config}}{{.Subnet}} {{end}}'
A conflict exists when a br-xxxxxxxxxxxx or docker0 route falls inside a subnet also used by your physical interface, VPN (tun0, wg0), or upstream LAN.
2. Choose a free address range
Pick a range absent from ip route output and from any site-to-site VPN or corporate network you connect to. Ranges within 10.0.0.0/8 that are rarely used (e.g. 10.200.0.0/16) are less likely to collide than the 172.16.0.0/12 or 192.168.0.0/16 defaults.
3. Edit the Docker daemon configuration
Edit /etc/docker/daemon.json (create it if absent; keep any existing keys such as registry-mirrors or log-driver):
{
"bip": "10.200.0.1/24",
"default-address-pools": [
{ "base": "10.201.0.0/16", "size": 24 }
]
}
bipsets thedocker0bridge address.default-address-poolssets the pool used for every user-defined network (compose stacks, aaPanel app installs).sizeis the prefix length of each allocated subnet;24with a/16base yields 256 networks.
Validate the JSON before restarting:
python3 -m json.tool /etc/docker/daemon.json
4. Restart Docker and recreate existing networks
Existing networks keep their old subnets. Stop the affected stacks, remove the networks, and bring them up again:
systemctl restart docker
cd /www/dk_project/<project_name> # aaPanel compose project path
docker compose down
docker compose up -d
Networks defined outside compose that are no longer in use can be removed with:
docker network prune
Perform docker compose down before the restart if you want the bip change to apply cleanly to running containers, since containers with restart: always come back up on the old network otherwise.
5. Pin a subnet for specific stacks (optional)
To avoid dependence on the pool, declare the subnet explicitly in the compose file (edit via aaPanel > Docker > Compose > Edit, or in /www/dk_project//docker-compose.yml):
networks:
default:
ipam:
config:
- subnet: 10.202.10.0/24
6. Verify
ip -4 route show | grep -E 'docker0|br-'
docker network inspect bridge --format '{{range .IPAM.Config}}{{.Subnet}}{{end}}'
ping -c 2 <previously_unreachable_lan_host>
Why This Fails for Some Users
- Invalid or partial JSON: A trailing comma or missing brace in
daemon.jsonpreventsdockerdfrom starting. Checkjournalctl -u docker -n 50 --no-pager. - aaPanel-installed Docker uses a different config path: Snap-installed Docker reads /var/snap/docker/current/config/daemon.json, not
/etc/docker/daemon.json. Rootless Docker reads ~/.config/docker/daemon.json. bipconflicts withdefault-address-pools: Thebipsubnet must not overlap the pool base, ordockerdfails with an overlapping pool error.- Existing networks retain old subnets: Changing
daemon.jsonnever modifies existing networks. Containers restarted on a persistentbr-xxxxnetwork keep the conflicting range until the network is removed and recreated. - Stale routes after removal: If a route for the old bridge persists after removing the network, delete it manually:
ip route del 172.18.0.0/16. - Pool exhaustion: A
/24base withsize: 24provides only one network. Ensure the base prefix is shorter thansize(e.g./16base with/24size). - VPN connected after Docker starts: If a VPN pushes a route overlapping an existing bridge subnet, the conflict appears only when the VPN is active. Address pools cannot detect this; choose a range outside the VPN’s advertised routes.
- Firewall interaction: With
ufworfirewalld, a changed subnet may need updated rules for the new range. Under firewalld, add the docker zone:firewall-cmd --permanent --zone=docker --add-source=10.201.0.0/16 && firewall-cmd --reload. - Docker version constraints:
default-address-poolsrequires Docker 17.06 or later. Older versions ignore it silently. - aaPanel-managed containers with static IPs: Containers configured with fixed addresses in the old range fail to start after a subnet change. Update the address in the compose file or container configuration.
- Swarm mode: Overlay networks use a separate pool controlled by
docker swarm init --default-addr-pool, notdaemon.json.