Solving Docker Container Internet Connectivity Issues When Host VPN is Active


7 views

When running a VPN client on the host machine, standard Docker containers lose internet connectivity while using bridge networking. The issue manifests when trying basic network operations like ping:

# Fails when VPN is active
docker run adiazmor/docker-ubuntu-with-ping ping 8.8.8.8

# Works (but isn't ideal)
docker run --net=host adiazmor/docker-ubuntu-with-ping ping 8.8.8.8

The routing tables reveal significant changes when the VPN activates:

# Host routing table WITHOUT VPN
0.0.0.0         192.168.1.254   0.0.0.0         UG    100    0        0 enp4s0
172.17.0.0      0.0.0.0         255.255.0.0     U     0      0        0 docker0

# Host routing table WITH VPN
0.0.0.0         192.168.226.1   0.0.0.0         UG    0      0        0 tap0
0.0.0.0         192.168.1.254   0.0.0.0         UG    100    0        0 enp4s0

The VPN client creates a new default route with higher priority (lower metric) than the original route. Docker's bridge networking relies on NAT and IP forwarding through the host's main network interface, but the VPN redirects this traffic.

Option 1: Modify VPN Routing Policies

Adjust your VPN configuration to exclude Docker networks from routing through the VPN tunnel. For IKE-based VPNs:

# Example IKEv2 config modification
s:policy-list-exclude:172.17.0.0/16
s:policy-list-exclude:172.18.0.0/16
s:policy-list-exclude:172.19.0.0/16

Option 2: Configure Docker's MTU

VPNs often use smaller MTU sizes. Set Docker's MTU to match:

# In /etc/docker/daemon.json
{
  "mtu": 1380
}

Option 3: Create a Dedicated Docker Network

Establish a network with specific gateway settings:

docker network create --driver=bridge \
  --subnet=172.25.0.0/16 \
  --gateway=172.25.0.1 \
  --opt com.docker.network.bridge.name=docker-vpn \
  vpn-enabled-net

For compose-based deployments where --net=host isn't practical:

version: '3.8'
services:
  app:
    network_mode: bridge
    networks:
      vpn_net:
        ipv4_address: 172.25.0.2

networks:
  vpn_net:
    driver: bridge
    driver_opts:
      com.docker.network.bridge.name: docker-vpn
    ipam:
      config:
        - subnet: 172.25.0.0/16
          gateway: 172.25.0.1

After implementing any solution, verify with:

docker run --rm --net=vpn-enabled-net alpine ping -c 4 8.8.8.8
docker run --rm alpine route -n
iptables -t nat -L -n -v

When establishing a VPN connection on your host machine, you might encounter a situation where Docker containers lose internet connectivity. This occurs because:

# Host routing table with VPN active
Destination     Gateway         Genmask         Flags Metric Ref    Use Iface
0.0.0.0         192.168.226.1   0.0.0.0         UG    0      0        0 tap0
0.0.0.0         192.168.1.254   0.0.0.0         UG    100    0        0 enp4s0

The VPN creates a new default route with higher priority (metric 0), while Docker's bridge networks remain isolated from this routing path.

A quick test reveals the problem:

docker run --rm alpine ping 8.8.8.8
# Fails when VPN is active

docker run --net=host --rm alpine ping 8.8.8.8
# Works because it bypasses Docker networking

The container's routing table shows the isolation:

Destination     Gateway         Genmask         Flags Metric Ref    Use Iface
0.0.0.0         172.19.0.1      0.0.0.0         UG    0      0        0 eth0
172.19.0.0      0.0.0.0         255.255.0.0     U     0      0        0 eth0

Option 1: Route VPN Traffic Through Docker

Add specific routes for your VPN endpoints:

sudo ip route add 78.109.86.184 via 192.168.1.254 dev enp4s0

Option 2: Configure Docker DNS Settings

Create/update your Docker daemon configuration:

# /etc/docker/daemon.json
{
  "dns": ["8.8.8.8", "8.8.4.4"],
  "dns-opts": ["use-vc"]
}

Option 3: Custom Bridge Network with Gateway

docker network create --driver=bridge \
  --gateway=192.168.1.1 \
  --subnet=192.168.1.0/24 \
  my_custom_bridge

docker run --network=my_custom_bridge your_image

For persistent solutions, modify NAT rules:

sudo iptables -t nat -A POSTROUTING -o tap0 -j MASQUERADE
sudo iptables -A FORWARD -i docker0 -o tap0 -j ACCEPT
sudo iptables -A FORWARD -i tap0 -o docker0 -m state \
  --state RELATED,ESTABLISHED -j ACCEPT

For compose files where --net=host isn't viable:

version: '3'
services:
  app:
    network_mode: "service:vpn"
    depends_on:
      - vpn

  vpn:
    image: your_vpn_image
    network_mode: host
    # VPN configuration here