Reformatting README

This commit is contained in:
2026-07-30 12:39:01 +04:00
parent 3886d956cc
commit 730c3ffe36

View File

@@ -1,49 +1,37 @@
# ddev-tailscale-docker-fix # Docker + Tailscale Network Fix
> **Note:** these scripts were written by an LLM (Claude Code), diagnosing > **Note:** these scripts were written by an LLM (Claude Code), diagnosing and fixing an issue live on one specific
> and fixing an issue live on one specific machine (Fedora + NetworkManager > machine (Fedora + NetworkManager + Tailscale + Docker). They worked there, but review them yourself before running as
> + Tailscale + Docker). They worked there, but review them yourself before > root on another machine — network/firewall behavior varies across distros, Tailscale versions, and configurations.
> running as root on another machine — network/firewall behavior varies
> across distros, Tailscale versions, and configurations.
Fixes Docker containers (e.g. ddev) losing internet access when Tailscale Fixes Docker containers (e.g. ddev) losing internet access when Tailscale is running with an exit node ("route all
is running with an exit node ("route all traffic") enabled. traffic") enabled.
## The problem ## The problem
With an exit node and "route all traffic" enabled, Tailscale installs a With an exit node and "route all traffic" enabled, Tailscale installs a catch-all `ip rule` that captures every packet
catch-all `ip rule` that captures every packet not explicitly exempted not explicitly exempted and routes it out via the `tailscale0` interface. Tailscale exempts its own daemon traffic from
and routes it out via the `tailscale0` interface. Tailscale exempts its that capture with a firewall mark, but has no way to exempt traffic that only passes *through* the host (forwarded)
own daemon traffic from that capture with a firewall mark, but has no
way to exempt traffic that only passes *through* the host (forwarded)
rather than originating from it. rather than originating from it.
Docker containers' outbound traffic is exactly that: forwarded, not Docker containers' outbound traffic is exactly that: forwarded, not locally-originated. It gets no exemption, so it's
locally-originated. It gets no exemption, so it's swept into swept into Tailscale's catch-all route, sent out via `tailscale0`, and dropped by Tailscale's own anti-spoofing firewall
Tailscale's catch-all route, sent out via `tailscale0`, and dropped by rule (`ts-forward`) because it isn't a recognized Tailscale-tunneled flow.
Tailscale's own anti-spoofing firewall rule (`ts-forward`) because it
isn't a recognized Tailscale-tunneled flow.
Symptom: every DNS lookup / outbound HTTP request from inside a Symptom: every DNS lookup / outbound HTTP request from inside a container hangs until timeout whenever Tailscale is
container hangs until timeout whenever Tailscale is connected — WP core connected — WP core update checks, plugin license pings, `composer`/`npm` installs, anything. The host's own
update checks, plugin license pings, `composer`/`npm` installs, applications aren't affected, since their traffic is locally-originated and gets Tailscale's exemption mark.
anything. The host's own applications aren't affected, since their
traffic is locally-originated and gets Tailscale's exemption mark.
## The fix ## The fix
A higher-priority `ip rule` that routes Docker's private bridge subnet A higher-priority `ip rule` that routes Docker's private bridge subnet (default: `172.16.0.0/12`, covering Docker's
(default: `172.16.0.0/12`, covering Docker's default address pool) default address pool) through the normal routing table instead of Tailscale's catch-all. This doesn't weaken the VPN for
through the normal routing table instead of Tailscale's catch-all. This anything else on the host — it only affects packets forwarded from Docker's own bridge networks.
doesn't weaken the VPN for anything else on the host — it only affects
packets forwarded from Docker's own bridge networks.
The rule is kept in place by a systemd timer that re-checks/re-adds it The rule is kept in place by a systemd timer that re-checks/re-adds it every 15s (and 5s after boot), rather than hooked
every 15s (and 5s after boot), rather than hooked to a network event: to a network event: `tailscale0` is a tun device created directly by `tailscaled`, not through a NetworkManager
`tailscale0` is a tun device created directly by `tailscaled`, not connection profile, so NetworkManager never fires dispatcher/udev events we could reliably hook into for it (confirmed
through a NetworkManager connection profile, so NetworkManager never by testing — don't waste time re-trying that route).
fires dispatcher/udev events we could reliably hook into for it
(confirmed by testing — don't waste time re-trying that route).
## Install ## Install
@@ -51,9 +39,8 @@ fires dispatcher/udev events we could reliably hook into for it
sudo ./install.sh sudo ./install.sh
``` ```
Optional overrides if Docker's default address pool doesn't apply on Optional overrides if Docker's default address pool doesn't apply on your machine (e.g. you have 15+ concurrent Docker
your machine (e.g. you have 15+ concurrent Docker networks and Docker networks and Docker has fallen back to a different private range):
has fallen back to a different private range):
``` ```
sudo ./install.sh --subnet 172.16.0.0/12 --priority 5200 --table main sudo ./install.sh --subnet 172.16.0.0/12 --priority 5200 --table main
@@ -74,17 +61,12 @@ sudo ./uninstall.sh
## Caveats ## Caveats
- Only covers Docker networks within `172.16.0.0/12`. If you have more - Only covers Docker networks within `172.16.0.0/12`. If you have more than ~15 concurrent Docker networks on one
than ~15 concurrent Docker networks on one machine, Docker falls back machine, Docker falls back to `192.168.0.0/20` blocks and then `10.0.0.0/8` — those aren't covered by default. Don't
to `192.168.0.0/20` blocks and then `10.0.0.0/8` — those aren't just widen the subnet to `192.168.0.0/16` or `10.0.0.0/8` to compensate: those ranges are also real LAN/VPN
covered by default. Don't just widen the subnet to `192.168.0.0/16` subnet-route space, and blanket-exempting them could route traffic around the tunnel that's actually supposed to go
or `10.0.0.0/8` to compensate: those ranges are also real LAN/VPN through it. If you hit this, either raise `--subnet` to the specific overflow range Docker actually assigned, or
subnet-route space, and blanket-exempting them could route traffic extend the script to enumerate live `docker network inspect` subnets instead of using a fixed range.
around the tunnel that's actually supposed to go through it. If you - Assumes `tailscaled`'s own ip rules stay in the 5210+ priority range observed on the machine this was built on; if a
hit this, either raise `--subnet` to the specific overflow range future Tailscale version changes that, priority 5200 should still safely sit above it as long as it's below whatever
Docker actually assigned, or extend the script to enumerate live Tailscale uses for its catch-all rule.
`docker network inspect` subnets instead of using a fixed range.
- Assumes `tailscaled`'s own ip rules stay in the 5210+ priority range
observed on the machine this was built on; if a future Tailscale
version changes that, priority 5200 should still safely sit above it
as long as it's below whatever Tailscale uses for its catch-all rule.