aaPanel Docker manager bridge network conflicting with local host IP subnets

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.

See also  Resolving Cloudflare Error 525 (SSL Handshake Failed) on Origin CA Setup

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 }
  ]
}
  • bip sets the docker0 bridge address.
  • default-address-pools sets the pool used for every user-defined network (compose stacks, aaPanel app installs).
  • size is the prefix length of each allocated subnet; 24 with a /16 base 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.json prevents dockerd from starting. Check journalctl -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.
  • bip conflicts with default-address-pools: The bip subnet must not overlap the pool base, or dockerd fails with an overlapping pool error.
  • Existing networks retain old subnets: Changing daemon.json never modifies existing networks. Containers restarted on a persistent br-xxxx network 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 /24 base with size: 24 provides only one network. Ensure the base prefix is shorter than size (e.g. /16 base with /24 size).
  • 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 ufw or firewalld, 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-pools requires 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, not daemon.json.

Leave a Comment