Created
October 9, 2025 02:39
-
-
Save luislobo/a2d1d48982e9290e26a2d47c0f0613a2 to your computer and use it in GitHub Desktop.
Fix Docker bridge conflicts with Cisco VPN
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| #!/usr/bin/env bash | |
| # fix-docker-vpn.sh | |
| # Tames NetworkManager + Docker defaults so VPN clients stop restarting | |
| set -euo pipefail | |
| log() { printf '\n[%s] %s\n' "$(date +%H:%M:%S)" "$*"; } | |
| warn() { printf '\n[%s] WARNING: %s\n' "$(date +%H:%M:%S)" "$*" >&2; } | |
| require() { command -v "$1" >/dev/null 2>&1 || { warn "'$1' is required"; missing=1; }; } | |
| if [[ $EUID -ne 0 ]]; then | |
| warn "run this script with sudo or as root" | |
| exit 1 | |
| fi | |
| missing=0 | |
| require nmcli | |
| require systemctl | |
| require python3 | |
| require docker | |
| [[ $missing -eq 0 ]] || { warn "install missing commands first"; exit 1; } | |
| NM_CONF_DIR=/etc/NetworkManager/conf.d | |
| NM_CONF_FILE=$NM_CONF_DIR/docker-unmanaged.conf | |
| DOCKER_DAEMON=/etc/docker/daemon.json | |
| DOCKER_TMP=$(mktemp) | |
| NM_TMP=$(mktemp) | |
| cleanup() { rm -f "$DOCKER_TMP" "$NM_TMP"; } | |
| trap cleanup EXIT INT TERM | |
| log "Removing stale NetworkManager profile for docker0 (if present)" | |
| if nmcli --fields NAME,DEVICE connection show | grep -q '^docker0'; then | |
| nmcli connection delete docker0 || warn "failed to delete docker0 profile (continuing)" | |
| else | |
| log "No NetworkManager connection named 'docker0' found" | |
| fi | |
| log "Ensuring NetworkManager leaves Docker bridges unmanaged" | |
| cat <<'EONM' >"$NM_TMP" | |
| [keyfile] | |
| unmanaged-devices=interface-name:docker0;interface-name:veth*;interface-name:br-* | |
| EONM | |
| install -m 0644 -D "$NM_TMP" "$NM_CONF_FILE" | |
| log "Reloading NetworkManager" | |
| systemctl reload NetworkManager || systemctl restart NetworkManager | |
| log "Updating Docker default address pool to avoid VPN ranges" | |
| python3 - "$DOCKER_DAEMON" "$DOCKER_TMP" <<'PY' | |
| import json, sys | |
| from pathlib import Path | |
| daemon_path = Path(sys.argv[1]) | |
| tmp_path = Path(sys.argv[2]) | |
| desired = [{"base": "10.239.0.0/16", "size": 24}] | |
| data = {} | |
| if daemon_path.exists(): | |
| with daemon_path.open() as f: | |
| data = json.load(f) | |
| if data.get("default-address-pools") != desired: | |
| data["default-address-pools"] = desired | |
| tmp_path.write_text(json.dumps(data, indent=4) + "\n") | |
| tmp_path.replace(daemon_path) | |
| sys.exit(10) # signal caller to restart Docker | |
| sys.exit(0) | |
| PY | |
| docker_updated=$? | |
| if [[ $docker_updated -eq 10 ]]; then | |
| log "Restarting Docker to apply address pool change" | |
| systemctl restart docker | |
| else | |
| log "Docker address pool already configured (no restart needed)" | |
| fi | |
| log "Verifying bridge network" | |
| docker network inspect bridge \ | |
| --format '{{range .IPAM.Config}}{{println " - Subnet:" .Subnet "Gateway:" .Gateway}}{{end}}' | |
| log "Done. You may need to reconnect VPN once so it picks up the new routing." |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment