fix(galactus): give containers Tailscale's resolver so MagicDNS names resolve

With the image fixed, the API got as far as connecting and then died with
Prisma P1001 "can't reach database server". The cause is DNS, not routing.

galactus runs systemd-resolved, whose 127.0.0.53 stub is unreachable from
inside a container, so Docker falls back to the upstream resolver in
/run/systemd/resolve/resolv.conf — the LAN router, which knows nothing about
the tailnet. Verified from a probe container on galactus: resolving
galactus.tail01aa2.ts.net fails outright, while `nc 100.103.77.46 3306` is
OPEN. Only the lookup was broken.

Pin the api and web services to Tailscale's own resolver (100.100.100.100,
the same anycast address on every tailnet) with this tailnet's search suffix.
Both are overridable via TAILSCALE_DNS / TAILNET_SUFFIX. db and minio need
nothing — they make no outbound calls.

Verified end to end: the published image, unmodified, with only these DNS
settings, boots on galactus against the real database and serves
  /health   {"status":"ok"}
  /version  {"service":"api","version":"master","gitSha":"3ff56e6b..."}

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
2026-07-30 14:47:19 -07:00
co-authored by Claude Opus 5
parent 3ff56e6b72
commit 1cba9bfc32
2 changed files with 44 additions and 0 deletions
@@ -11,6 +11,16 @@
# name galactus's own address and the published port — exactly as on cubex
# today. Do not "simplify" them to `mysql:3306`.
#
# ...which means these containers have to resolve galactus's MagicDNS name, and
# by default they CANNOT. The host runs systemd-resolved, whose 127.0.0.53 stub
# is unreachable from a container, so Docker falls back to the upstream resolver
# in /run/systemd/resolve/resolv.conf — the LAN router, which knows nothing
# about the tailnet. Routing to 100.x works fine; only the lookup fails, and the
# API dies with Prisma P1001 "can't reach database server". Pointing the
# containers at Tailscale's own resolver fixes it. 100.100.100.100 is Tailscale's
# fixed anycast MagicDNS address (identical on every tailnet); the search domain
# is this tailnet's suffix.
#
# The web image is NOT URL-baked: the browser's API origin is injected at
# runtime from API_ORIGIN (apps/web/src/app/layout.tsx), so the same image works
# for any deployment. APP_VERSION / GIT_SHA / BUILD_DATE come baked in from
@@ -27,6 +37,10 @@ services:
# survives stack renames; the compose service name does not.
labels:
io.jorgecuadros.role: "api"
dns:
- ${TAILSCALE_DNS:-100.100.100.100}
dns_search:
- ${TAILNET_SUFFIX:-tail01aa2.ts.net}
environment:
DATABASE_URL: ${DATABASE_URL:?DATABASE_URL must be set}
SESSION_SECRET: ${SESSION_SECRET:?SESSION_SECRET must be set}
@@ -59,6 +73,12 @@ services:
restart: unless-stopped
labels:
io.jorgecuadros.role: "web"
# Next server-side rendering can call the API by API_ORIGIN, which is the
# same MagicDNS name — so the web container needs the resolver too.
dns:
- ${TAILSCALE_DNS:-100.100.100.100}
dns_search:
- ${TAILNET_SUFFIX:-tail01aa2.ts.net}
environment:
# Public API URL the browser calls (injected at runtime, see layout.tsx).
API_ORIGIN: ${API_ORIGIN:?API_ORIGIN must be set}