tailscale serve (configured through the official Tailscale app's own web UI, from the default getumbrel/umbrel-apps store) can claim a port number that another installed app's app_proxy container also publishes via Docker. When both land on the same port, whichever binds second fails — Docker's 0.0.0.0: wildcard publish and Tailscale's specific-interface bind on the same port cannot coexist once one is live.
This surfaces as the losing app's app_proxy_1 container stuck in Created state with:
failed to set up container networking: driver failed programming external connectivity on endpoint _app_proxy_1: failed to bind host port for 0.0.0.0::...: address already in use
...any time that container needs a fresh start (app restart, host reboot, umbrel.service restart) after tailscale serve's listener wins the race for that port. The app's dashboard tile then looks "stuck restarting" — this reads as a cosmetic display bug but is actually a genuinely broken dependency in at least one case we confirmed (lightning_app_proxy_1, the Lightning app's own web UI proxy, sat broken for hours after a restart before we traced it back to this).
Root cause
Two independent systems with no shared port registry:
umbreld's app port allocator assigns each app's app_proxy a fixed, declared host port (from that app's docker-compose.yml).
Tailscale's serve feature is a raw runtime binding, configured by the user through Tailscale's own UI, that claims a port on the tailnet interface pointing at 127.0.0.1:<some-app's-port>. umbreld has zero visibility into what ports the tailscaled process inside its own container has bound, since that state lives entirely inside Tailscale's own runtime, never passed through umbreld's port bookkeeping.
This is the same underlying blind spot as #2167 / #2197 (the tor_server sidecar crash-looping for Tailscale specifically, because umbreld's shared app-infrastructure code assumes every app has a normal app_proxy container, which Tailscale — running network_mode: host — never gets). Different symptom, same root cause: Tailscale's unusual networking model isn't special-cased anywhere in umbreld's shared app-management code.
Steps to reproduce
Install any app with a fixed app_proxy port (e.g. Thunderhub on 3000).
In the Tailscale app, configure tailscale serve --https=3000 http://127.0.0.1:3000 (or any rule using that app's exact port number).
Restart umbrel.service, reboot the host, or otherwise force that app's container to recreate.
Observe: the app's app_proxy_1 container is stuck in Created, docker logs/docker inspect shows the port-bind error above, and the app's dashboard tile appears stuck.
We hit this on 7 separate ports across one node in two incidents (3000, 3001, 3004, 3777, 8889 — visibly broken; 2100, 2101, 3030 — 2 latent, 1 actively broken, found via a full audit of every declared Docker port against the live serve list).
Suggested fix directions
umbreld's app port allocator could check live tailscale serve bindings (via tailscale serve status --json inside the Tailscale container, if reachable) before assigning/recreating an app_proxy port, and warn or refuse on conflict.
Alternatively, document this clearly in the Tailscale app's own UI when a user sets up a serve rule — warn if the chosen port matches a currently-installed app's own app_proxy port.
At minimum, a docs note recommending users pick tailnet-serve ports well outside the range Umbrel apps use for app_proxy (we worked around it ourselves with a +10000 offset scheme) would help other operators avoid rediscovering this from scratch.
tailscale serve (configured through the official Tailscale app's own web UI, from the default getumbrel/umbrel-apps store) can claim a port number that another installed app's app_proxy container also publishes via Docker. When both land on the same port, whichever binds second fails — Docker's 0.0.0.0: wildcard publish and Tailscale's specific-interface bind on the same port cannot coexist once one is live.
This surfaces as the losing app's app_proxy_1 container stuck in Created state with:
failed to set up container networking: driver failed programming external connectivity on endpoint _app_proxy_1: failed to bind host port for 0.0.0.0::...: address already in use
...any time that container needs a fresh start (app restart, host reboot, umbrel.service restart) after tailscale serve's listener wins the race for that port. The app's dashboard tile then looks "stuck restarting" — this reads as a cosmetic display bug but is actually a genuinely broken dependency in at least one case we confirmed (lightning_app_proxy_1, the Lightning app's own web UI proxy, sat broken for hours after a restart before we traced it back to this).
Root cause
Two independent systems with no shared port registry:
umbreld's app port allocator assigns each app's app_proxy a fixed, declared host port (from that app's docker-compose.yml).
Tailscale's serve feature is a raw runtime binding, configured by the user through Tailscale's own UI, that claims a port on the tailnet interface pointing at 127.0.0.1:<some-app's-port>. umbreld has zero visibility into what ports the tailscaled process inside its own container has bound, since that state lives entirely inside Tailscale's own runtime, never passed through umbreld's port bookkeeping.
This is the same underlying blind spot as #2167 / #2197 (the tor_server sidecar crash-looping for Tailscale specifically, because umbreld's shared app-infrastructure code assumes every app has a normal app_proxy container, which Tailscale — running network_mode: host — never gets). Different symptom, same root cause: Tailscale's unusual networking model isn't special-cased anywhere in umbreld's shared app-management code.
Steps to reproduce
Install any app with a fixed app_proxy port (e.g. Thunderhub on 3000).
In the Tailscale app, configure tailscale serve --https=3000 http://127.0.0.1:3000 (or any rule using that app's exact port number).
Restart umbrel.service, reboot the host, or otherwise force that app's container to recreate.
Observe: the app's app_proxy_1 container is stuck in Created, docker logs/docker inspect shows the port-bind error above, and the app's dashboard tile appears stuck.
We hit this on 7 separate ports across one node in two incidents (3000, 3001, 3004, 3777, 8889 — visibly broken; 2100, 2101, 3030 — 2 latent, 1 actively broken, found via a full audit of every declared Docker port against the live serve list).
Suggested fix directions
umbreld's app port allocator could check live tailscale serve bindings (via tailscale serve status --json inside the Tailscale container, if reachable) before assigning/recreating an app_proxy port, and warn or refuse on conflict.
Alternatively, document this clearly in the Tailscale app's own UI when a user sets up a serve rule — warn if the chosen port matches a currently-installed app's own app_proxy port.
At minimum, a docs note recommending users pick tailnet-serve ports well outside the range Umbrel apps use for app_proxy (we worked around it ourselves with a +10000 offset scheme) would help other operators avoid rediscovering this from scratch.