docs(deploy): runbook + fixes from the limonia.net cutover

Document what we learned doing the real NetBird+Traefik migration:

- Keycloak realm/client names are operator-chosen; the realm-export.json's
  literal "colectivo-web" / "colectivo" values are illustrative, not required.
- Legacy Keycloak /auth/ base path: PUBLIC_KEYCLOAK_URL must include the
  suffix when the deployment serves realms under /auth/realms/... (hit this
  on auth.fosil.eu). Verify with the discovery URL returning 200.
- NetBird's installer deploys Traefik with idleTimeout=0 (unlimited) by
  default — verify instead of prescribing 3600s.
- Runbook: --no-cache fixes the intermittent vite SSR "transforming..."
  hang that surfaces as a PostHog shutdown timeout.
- Runbook: any PUBLIC_* change needs an app rebuild (build args); secret
  changes only need `docker compose restart auth`.
- Runbook: TRUNCATE recipe for wiping all app + auth data while keeping
  schema + migration tracking intact.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-04-24 01:58:00 +02:00
parent 33b32cae4a
commit dee9ee8014
3 changed files with 49 additions and 13 deletions

View File

@@ -166,14 +166,15 @@ Defined in `keycloak/realm-export.json` and `supabase/seed.sql`. All use passwor
- Test the shopping session mode on a real iPhone during development, not only in DevTools.
**Traefik (prod) — WebSocket for Realtime:**
- Traefik routes `Host(erosi.limonia.net) && PathPrefix(/rest/v1, /auth/v1, /realtime/v1, ...)` to Kong via Docker-provider labels (priority 100); everything else on the same host falls through to the SvelteKit app (priority 1). Routing lives on the `kong` and `app` services' `labels:` block in `infra/docker-compose.erosi.yml` — no Traefik config files.
- The HTTPS entrypoint must set `--entryPoints.websecure.transport.respondingTimeouts.idleTimeout=3600s` in Traefik's static config (NetBird-managed container — edit once when setting up). Default is ~180s; without the bump, Realtime WebSockets drop mid-shopping-session and the client reconnects continuously.
- Traefik routes `Host(erosi.limonia.net) && PathPrefix(/rest/v1, /auth/v1, /realtime/v1, ...)` to Kong via Docker-provider labels (priority 100); everything else on the same host falls through to the SvelteKit app (priority 1). Routing lives on the `kong` and `app` services' `labels:` block in `infra/docker-compose.erosi.yml` — no Traefik config files. NetBird's self-hosted installer (≥ v0.65) deploys Traefik with `--providers.docker=true` + `--providers.docker.exposedByDefault=false`, so our labeled services are auto-discovered on the `netbird_netbird` Docker network.
- The HTTPS entrypoint needs `respondingTimeouts.idleTimeout` set to either `0` (unlimited, NetBird's default) or `≥3600s`. At the default ~180s, Realtime WebSockets drop mid-shopping-session and the client reconnects continuously. Verify with `docker inspect netbird-traefik --format '{{range .Args}}{{.}}{{"\n"}}{{end}}' | grep idleTimeout`.
- Traefik auto-sets `X-Forwarded-{Proto,Host,Port,For}` when `tls.certresolver` is configured on the router, so no extra middleware is needed for the app.
- CSP `connect-src` must include both `https://erosi.limonia.net` and `wss://erosi.limonia.net`.
**External Keycloak:**
- Keycloak runs outside this stack; `PUBLIC_KEYCLOAK_URL` in `.env` points at it. Whatever proxy sits in front of it must forward `X-Forwarded-{Host,Port,Proto}` or Keycloak builds redirect URIs with internal ports and the OIDC flow breaks — that's the external operator's concern, not ours.
- The realm + client configuration required on the external Keycloak is documented in `docs/deployment.md` and mirrored in `keycloak/realm-export.erosi.json` (kept as reference; not imported by this stack).
- The realm + client configuration required on the external Keycloak is documented in `docs/deployment.md` and mirrored in `keycloak/realm-export.erosi.json` (kept as reference; not imported by this stack). Realm name, client id, and client secret are operator-chosen and wired in via `.env` — the file's literal `colectivo-web` / `colectivo` values are illustrative, not required.
- **Legacy Keycloak `/auth/` base path**: Keycloak ≤ 16 (and some later distributions) serve realms under `/auth/realms/...` instead of `/realms/...`. In that case `PUBLIC_KEYCLOAK_URL` must include the suffix, e.g. `https://auth.example.com/auth`. Symptom of a missing `/auth` suffix: `GOTRUE_EXTERNAL_KEYCLOAK_URL/...well-known/openid-configuration` returns 404; the Supabase login redirect lands on a Keycloak error page. Verify before deploying: `curl -I ${PUBLIC_KEYCLOAK_URL}/realms/${PUBLIC_KEYCLOAK_REALM}/.well-known/openid-configuration` must return 200.
**Supabase Realtime self-hosted:**
- Check `MAX_REPLICATION_SLOTS` in PostgreSQL and `REALTIME_MAX_CONNECTIONS` before going to production.