HowlCastHowlCast
Deploy

Secrets

Every secret HowlCast reads, where to set it, and what happens if you don't.

HowlCast reads ~15 distinct secrets across local dev and prod. Most fail soft (feature disabled with a clear error) but a few will refuse to boot.

Inventory

SecretWhere it goesRequired?What breaks without it
BETTER_AUTH_SECRETboth WorkersyesAuth refuses to start
BETTER_AUTH_URLboth WorkersyesMagic-link callbacks point at wrong host
CORS_ORIGINboth WorkersyesAPI rejects browser requests
STREAM_API_KEYapi Workerfor livestream.* procedures throw PRECONDITION_FAILED
STREAM_API_SECRETapi Workerfor liveSame as above + webhooks fail HMAC
TWITCH_CLIENT_IDapi Workerfor setupSetup wizard + emote pipeline disabled
TWITCH_CLIENT_SECRETapi Workerfor setupSame as above
RESEND_API_KEYapi Workerfor prodMagic-link emails fall through to console (no send)
MAIL_FROMapi Workerfor prodResend rejects the request
SMTP_URLapi Worker (dev only)nomailpit fallback in local dev
CLOUDFLARE_API_TOKENlocal + GH ActionsyesAlchemy can't deploy
CLOUDFLARE_ACCOUNT_IDlocal + GH ActionsyesAlchemy can't find the account
ALCHEMY_PASSWORDlocal + GH ActionsyesAlchemy state encryption
ALCHEMY_STATE_TOKENGH Actions onlyCI onlyCI deploy can't read its state Worker

Local dev

Copy the example file:

cp apps/server/.env.example apps/server/.env

Edit and fill in. The web app reads NEXT_PUBLIC_* vars at build time; the api reads via packages/env/src/server.ts.

Production (via Alchemy)

alchemy.run.ts wraps every secret in alchemy.secret(process.env.X ?? ""). On bun run deploy, alchemy uploads each secret to the Worker via the Cloudflare API. They're encrypted at rest in the Worker config.

Empty string is treated as "not configured" — the runtime then throws a clear error if you try to use that feature.

CI/CD (GitHub Actions)

.github/workflows/deploy.yml runs bun run deploy against your account. It needs all 14 secrets above pushed into the repo's GH Actions secrets:

gh secret set CLOUDFLARE_API_TOKEN
gh secret set CLOUDFLARE_ACCOUNT_ID
# … repeat for each

Use a scoped Cloudflare API token — not the root one. Required permissions: Workers Scripts:Edit, D1:Edit, R2:Edit, KV:Edit, Workers Routes:Edit, Account Settings:Read.

Rotation

  • STREAM_API_SECRET — rotate in GetStream dashboard, push new value, redeploy. Webhooks downtime ≈ 1 min.
  • BETTER_AUTH_SECRET — rotating invalidates all sessions. Plan downtime.
  • RESEND_API_KEY — rotate in Resend dashboard, push new value, redeploy. Magic-link emails skip until the new value is live.

Next: First deploy.

On this page