The starter is Cloudflare-first. Production resources are declared once in the root alchemy.run.ts using Alchemy v2 and provisioned with a single command.
What gets provisioned
- One D1 database shared by
apps/web,apps/api, andapps/background - One Worker per app (web, api, background), each with
placement.mode = smartwhere applicable - A Cloudflare Queue plus dead-letter queue (
b2b-saas-starter-webhooks+b2b-saas-starter-webhooks-dlq) bound as aQueueConsumeron the background worker - Cloudflare
SendEmailbinding withallowedSenderAddressesfor outbound transactional mail (env-gated) - Cloudflare
RateLimitbindings on the web worker (RATE_LIMITER_AUTH_READ,RATE_LIMITER_AUTH_WRITE,RATE_LIMITER_AUTH_SIGN_IN) and on the api worker (RATE_LIMITER_REST,RATE_LIMITER_REST_WRITE,RATE_LIMITER_INVITATIONS,RATE_LIMITER_ASSISTANT,RATE_LIMITER_MCP)
Deploy
bun run deploybun run deploy is bun alchemy.run.ts: it executes the Alchemy Stack programmatically rather than going through a separate CLI. Each Worker also has a hand-maintained wrangler.jsonc that mirrors the Alchemy declaration so wrangler dev and wrangler d1 migrations apply keep working locally; Alchemy is the source of truth for production.
Environment configuration
Sensitive values are wrapped in effect/Redacted inside alchemy.run.ts so they never appear in logs or stack traces. Optional providers are validated in packages/env/src/server.ts: missing config keeps the relevant provider inactive rather than failing startup. See the secret matrix in ARCHITECTURE.md for the full list (BETTER_AUTH_SECRET, BETTER_AUTH_URL, BETTER_AUTH_TRUSTED_ORIGINS, STRIPE_SECRET_KEY/STRIPE_WEBHOOK_SECRET, SENTRY_DSN, POSTHOG_KEY/POSTHOG_HOST, CLOUDFLARE_EMAIL_FROM, TURNSTILE_SITE_KEY/TURNSTILE_SECRET_KEY, WORKERS_AI_ENABLED, OPENAI_API_KEY).
Preview deployments per pull request
Every pull request opened from this repository gets its own Alchemy stage, pr-<number>, with a separate D1 database, webhook queues, and three Workers named b2b-saas-starter-pr-<number>-{web,api,background}. The Preview workflow deploys the stage on every push, runs the migrations, seeds the Seed Workspace so the preview opens with data, and keeps one comment on the pull request pointing at the web, API, and background URLs. Closing the pull request destroys the stage.
Previews run provider-light: Turnstile, Stripe, Sentry, PostHog, email, and AI stay off regardless of what the deploying environment has configured, and ENVIRONMENT is preview. The same stage can be deployed from a laptop:
export CLOUDFLARE_API_TOKEN=… CLOUDFLARE_ACCOUNT_ID=… CLOUDFLARE_WORKERS_SUBDOMAIN=<your subdomain>
export BETTER_AUTH_SECRET="$(openssl rand -base64 32)"
ALCHEMY_STAGE=pr-42 pnpm run deploy:stage
ALCHEMY_STAGE=pr-42 pnpm run db:seed:stage
ALCHEMY_STAGE=pr-42 pnpm run destroy:stageThe required secrets and the reasoning are in docs/deploying.md and ADR 0065.
Rolling back
bun run destroy (which is bun alchemy.run.ts --destroy) tears the entire stack down. Targeted rollbacks today go through redeploying a previous commit from CI; there is no versioned rollback flag in the starter's scripts.