Production environment checklist
Run before a Hetzner deploy (infra/hetzner/deploy-web-hetzner.sh) or after rotating secrets. Prod is self-hosted on Hetzner (Docker + Traefik) at https://app.stratifyinvest.com; Vercel is deactivated.
Dashboard (manual — Toby)
Supabase Auth
Prod auth is self-hosted Supabase on Hetzner. Set SITE_URL / API_EXTERNAL_URL on the Hetzner Supabase stack — do not supabase config push to the cloud project for Hetzner auth (the cloud project is Legacy; see docs/runbooks/hetzner-cutover.md):
- Site URL:
https://app.stratifyinvest.com - Redirect URLs:
https://app.stratifyinvest.com/**, localhost dev paths,stratify://**
./scripts/hetzner-update-public-urls.shsupabase config push --yes only applies if a stack is still on Supabase Cloud (Legacy). For the live Hetzner stack it has no effect on prod auth.Stripe
Not automated — no STRIPE_SECRET_KEY wired yet. In Stripe Dashboard:
- Developers → Webhooks → Add endpoint
- URL:
https://app.stratifyinvest.com/api/webhooks/stripe - Events:
checkout.session.completed,customer.subscription.*,invoice.*(minimum for subs) - Copy signing secret → set
STRIPE_WEBHOOK_SECRETin the Hetzner deploy env
(export before running deploy-web-hetzner.sh, which writes it into /data/stratify/web/.env).
- Add
STRIPE_SECRET_KEY(test or live) the same way for checkout/portal routes. - Products → Subscription product → monthly price — copy the price id
into STRIPE_PRICE_ID (checked by apps/web/lib/env/production.ts; startCheckout() errors with missing_price without it).
Hetzner deploy env vars (required in Production)
Written into /data/stratify/web/.env by infra/hetzner/deploy-web-hetzner.sh (public Supabase vars + CRON_SECRET from Keychain; Stripe/Resend/etc. from the exported shell env via append_env).
| Variable | Notes |
|---|---|
NEXT_PUBLIC_SUPABASE_URL | EU project |
NEXT_PUBLIC_SUPABASE_ANON_KEY | Public |
SUPABASE_SERVICE_ROLE | Sensitive — server only |
CRON_SECRET | Bearer for /api/cron/* |
PARTNER_WEBHOOK_SECRET | Not change-me |
RESEND_API_KEY | Email drainers |
STRIPE_SECRET_KEY | Checkout |
STRIPE_WEBHOOK_SECRET | Inbound Stripe |
STRIPE_PRICE_ID | startCheckout() errors missing_price without it |
VAPID_PRIVATE_KEY | Web push |
NEXT_PUBLIC_VAPID_PUBLIC_KEY | Web push |
NEXT_PUBLIC_APP_ENV | Set to production |
Optional: SUPABASE_WEBHOOK_SECRET, Sentry DSNs.
Admin analytics (PostHog embed)
| Variable | Notes |
|---|---|
NEXT_PUBLIC_POSTHOG_KEY | Client capture (landing) |
NEXT_PUBLIC_POSTHOG_HOST | Default https://eu.posthog.com |
NEXT_PUBLIC_POSTHOG_PROJECT_ID | External link on /analytics |
NEXT_PUBLIC_POSTHOG_EMBED_URL | Full iframe src from PostHog Share → Embed |
NEXT_PUBLIC_POSTHOG_EMBEDDED_INSIGHT_ID | Alternative: builds /embedded/{id} |
Publish gate (pilot fan-out)
| Variable | Effect |
|---|---|
STRATIFY_SANDBOX_MODE=1 | Allows signal fan-out without a live pilot (integration tests) |
| unset | Requires ≥1 row in pilots with status = live before non-demo publish |
Runtime guard
On Node startup (instrumentation.ts), production logs missing/insecure env via apps/web/lib/env/production.ts. Check container logs after deploy (docker logs stratify-web):
[stratify] Production env check FAILED
missing: ...Fix vars and redeploy.
Verify after deploy
# Cron auth smoke (replace URL + secret)
curl -sS -H "Authorization: Bearer $CRON_SECRET" \
"https://app.stratifyinvest.com/api/cron/drain-webhooks"Partner webhook HMAC verifier:
pnpm exec tsx scripts/verify-partner-webhook.ts --helpRelated
docs/secrets.md— Keychain mapping locallydocs/api/quickstart.md— B2B partner integrationdocs/critical-path.md— long-lead dependencies