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