Stratify
Engineering

Production Env

docs/runbooks/production-env.md

Source updated 03. Aug. 2026

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.sh
supabase 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:

  1. Developers → Webhooks → Add endpoint
  2. URL: https://app.stratifyinvest.com/api/webhooks/stripe
  3. Events: checkout.session.completed, customer.subscription.*, invoice.* (minimum for subs)
  4. Copy signing secret → set STRIPE_WEBHOOK_SECRET in the Hetzner deploy env

(export before running deploy-web-hetzner.sh, which writes it into /data/stratify/web/.env).

  1. Add STRIPE_SECRET_KEY (test or live) the same way for checkout/portal routes.
  2. 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).

VariableNotes
NEXT_PUBLIC_SUPABASE_URLEU project
NEXT_PUBLIC_SUPABASE_ANON_KEYPublic
SUPABASE_SERVICE_ROLESensitive — server only
CRON_SECRETBearer for /api/cron/*
PARTNER_WEBHOOK_SECRETNot change-me
RESEND_API_KEYEmail drainers
STRIPE_SECRET_KEYCheckout
STRIPE_WEBHOOK_SECRETInbound Stripe
STRIPE_PRICE_IDstartCheckout() errors missing_price without it
VAPID_PRIVATE_KEYWeb push
NEXT_PUBLIC_VAPID_PUBLIC_KEYWeb push
NEXT_PUBLIC_APP_ENVSet to production

Optional: SUPABASE_WEBHOOK_SECRET, Sentry DSNs.

Admin analytics (PostHog embed)

VariableNotes
NEXT_PUBLIC_POSTHOG_KEYClient capture (landing)
NEXT_PUBLIC_POSTHOG_HOSTDefault https://eu.posthog.com
NEXT_PUBLIC_POSTHOG_PROJECT_IDExternal link on /analytics
NEXT_PUBLIC_POSTHOG_EMBED_URLFull iframe src from PostHog Share → Embed
NEXT_PUBLIC_POSTHOG_EMBEDDED_INSIGHT_IDAlternative: builds /embedded/{id}

Publish gate (pilot fan-out)

VariableEffect
STRATIFY_SANDBOX_MODE=1Allows signal fan-out without a live pilot (integration tests)
unsetRequires ≥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 --help

Related

  • docs/secrets.md — Keychain mapping locally
  • docs/api/quickstart.md — B2B partner integration
  • docs/critical-path.md — long-lead dependencies