Deploy to production
Put your app online: a hosted Supabase project, the web app on Vercel, the environment variables, Supabase Auth settings, the two webhooks, and the phone app's production build.
Going live takes six steps: a hosted Supabase project with your schema, the web app on Vercel, its environment variables, Supabase Auth pointed at your domain, two webhooks, and a test run. Plan an afternoon for the first time. This page is the short version; the full guides are in the repository's docs/going-to-production/ folder, starting with checklist.mdoc, including Cloudflare, Docker and your own server instead of Vercel.
Before you start
You need accounts with:
- Supabase for the database, sign-in and file storage
- Vercel to host the web app
- Stripe or Lemon Squeezy for payments
- An email provider: Resend, or any SMTP service
- Your domain, so you can point it at Vercel
1. Create the Supabase project and push the schema
Create a project in the Supabase dashboard and keep its project ref and database password. Then, from the repository root:
SUPABASE_PROJECT_REF=<your-project-ref> pnpm --filter web supabase:deploy
This links the hosted project and runs supabase db push, which applies every migration in apps/web/supabase/migrations/. Check the Table Editor afterwards: you should see accounts, subscriptions, invitations and the other kit tables.
Never push the seed. Don't add --include-seed, and never run apps/web/supabase/seed.sql against a hosted database. It creates test users with published passwords, two of them super admins.
2. Deploy the web app to Vercel
In Vercel, choose Add New Project and import your repository. Set Root Directory to apps/web; leave the framework (Next.js), build command and output directory on their defaults. The repository is a monorepo, so the build fails without the root directory.
The first deploy usually fails because the environment variables aren't there yet. That's expected: add them in the next step and redeploy.
3. Set the environment variables
Put your production values in apps/web/.env.production.local (git ignores it). Run pnpm dev, open the developer tool at http://localhost:3010/variables and switch it to Production: it checks every value, including the ones another setting makes required. Use Copy to paste the result into Vercel (Settings, then Environment Variables), then redeploy.
The ones every app needs:
| Variable | Value |
|---|---|
NEXT_PUBLIC_SITE_URL | Your domain with https:// and no trailing slash |
NEXT_PUBLIC_PRODUCT_NAME | Your product's name |
NEXT_PUBLIC_SUPABASE_URL | Supabase: Project Settings, then API |
NEXT_PUBLIC_SUPABASE_PUBLIC_KEY | The project's publishable key |
SUPABASE_SECRET_KEY | The project's secret key. It skips row-level security, so it stays on the server |
SUPABASE_DB_WEBHOOK_SECRET | A random string: openssl rand -base64 32 |
NEXT_PUBLIC_BILLING_PROVIDER | stripe or lemon-squeezy |
NEXT_PUBLIC_STRIPE_PUBLISHABLE_KEY, STRIPE_SECRET_KEY, STRIPE_WEBHOOK_SECRET | From Stripe (step 5 gives you the webhook secret) |
LEMON_SQUEEZY_SECRET_KEY, LEMON_SQUEEZY_STORE_ID, LEMON_SQUEEZY_SIGNING_SECRET | Instead of the Stripe keys, if you use Lemon Squeezy |
MAILER_PROVIDER, EMAIL_SENDER | resend or nodemailer, and the address your emails come from |
RESEND_API_KEY | Only with Resend; the SMTP settings for nodemailer are in docs/going-to-production/production-environment-variables.mdoc |
NEXT_PUBLIC_AUTH_OAUTH_PROVIDERS | The social sign-ins you set up, such as google,github, or none. It defaults to google, so set it if you don't use Google |
Values that start with NEXT_PUBLIC_ are built into the page code, so changing one needs a redeploy. The build fails on some wrong public values, such as an http:// site URL. Server secrets are checked when they're first used, so a missing Stripe key shows up in the function logs as Please provide the variable STRIPE_SECRET_KEY.
4. Point Supabase Auth at your domain
In the Supabase dashboard:
- Authentication, then URL Configuration: set Site URL to
https://yourdomain.comand addhttps://yourdomain.com/auth/callback**to Redirect URLs. Match your domain exactly, includingwwwif you use it. - Authentication, then Email Templates: replace each template with the matching file in
apps/web/supabase/templates/(confirm email, magic link, reset password, invite, change email, one-time code). With Supabase's default templates, a link opened in a different browser than the one that asked for it fails. - Project Settings, then Authentication, then SMTP Settings: enter your email provider's SMTP details. Supabase's built-in sender is only for testing and allows very few emails an hour.
- For social sign-in, turn the providers on under Authentication, then Providers, and list the same ones in
NEXT_PUBLIC_AUTH_OAUTH_PROVIDERS.
5. Add the two webhooks
Both need your public domain: Vercel preview URLs sit behind a login, so webhooks can't reach them.
Payments. In Stripe, add a webhook endpoint for https://yourdomain.com/api/billing/webhook with these events: checkout.session.completed, checkout.session.async_payment_succeeded, checkout.session.async_payment_failed, customer.subscription.updated, customer.subscription.deleted and invoice.paid. Copy its signing secret into STRIPE_WEBHOOK_SECRET and redeploy. With Lemon Squeezy, point its webhook at the same URL and put its signing secret in LEMON_SQUEEZY_SIGNING_SECRET.
Database. In Supabase, open Database, then Webhooks, and create a hook on the subscriptions table for the DELETE event, sending to https://yourdomain.com/api/db/webhook with the header X-Supabase-Event-Signature set to your SUPABASE_DB_WEBHOOK_SECRET. It cancels the payment subscription when a subscription row is deleted, for example with its account. Webhooks explains both in more detail.
6. Check that it works
- Open
https://yourdomain.com/api/healthcheck. It should answer{"services":{"database":true}}. - Sign up with a real email address. The confirmation email should arrive from your provider, and its link should sign you in.
- Create a team and invite a second address.
- Buy a plan with Stripe in test mode first (card
4242 4242 4242 4242). The plan should show on the billing page, and the webhook in Stripe should list the events as delivered.
Then switch Stripe to live keys and a live webhook. On Supabase's free plan, a project with no activity for a week is paused, so move to a paid plan before real customers depend on it.
The phone app (All-in)
The phone app's EXPO_PUBLIC_ values are built into the app when you build it, and the repository only ships development values. Before your first release build, create apps/native/.env.production (or set the same values in EAS):
| Variable | Production value |
|---|---|
EXPO_PUBLIC_SUPABASE_URL | Your hosted Supabase project's URL |
EXPO_PUBLIC_SUPABASE_PUBLIC_KEY | Its publishable key |
EXPO_PUBLIC_API_BASE_URL | Your deployed web app, starting with https:// |
EXPO_PUBLIC_SITE_URL | The same web app, where the terms and privacy links open |
Without them the release build crashes on launch. Keep the sign-in settings the same as the web app's. Building and submitting to the App Store and Google Play is covered in docs/native/building-shipping.mdoc and docs/native/app-store-release.mdoc.
After launch
- Write your privacy policy, terms and cookie policy in
apps/web/content/legal/, and have a lawyer check them. - Add
apps/web/public/.well-known/security.txtso people know where to report a security problem. - Set up error monitoring:
docs/monitoring/covers Sentry. - Every schema change ships the same way as step 1: see Migrations.