TheShipStack Docs

Deployment

Deploy TheShipStack to production with Vercel, Neon, and Backblaze B2.

Prerequisites

  • Vercel account
  • Neon account (PostgreSQL)
  • Backblaze B2 account (file storage)
  • Resend account (email)
  • Google OAuth credentials (for Google sign-in)

1. Set up Neon

  1. Create a new project in Neon
  2. Copy the connection string from the dashboard
  3. Set DATABASE_URL in your Vercel env vars to the Neon connection string

2. Set up Backblaze B2

  1. Create a new bucket in B2 — set it to Public
  2. Create an App Key with read/write access to the bucket
  3. Note the endpoint, key ID, and app key

3. Set up Resend

  1. Create a Resend account and verify your sending domain
  2. Generate an API key
  3. Set EMAIL_FROM to an address on your verified domain

4. Deploy to Vercel

npx vercel

Or connect your GitHub repo in the Vercel dashboard and it will deploy on every push to main.

5. Set environment variables in Vercel

In Vercel → Project → Settings → Environment Variables, add all variables from your .env with production values:

  • DATABASE_URL — Neon connection string
  • BETTER_AUTH_SECRET — a new random secret for production
  • BETTER_AUTH_URL — your production URL (e.g. https://myapp.com)
  • GOOGLE_CLIENT_ID / GOOGLE_CLIENT_SECRET
  • STORAGE_ENDPOINT — B2 endpoint
  • STORAGE_ACCESS_KEY / STORAGE_SECRET_KEY / STORAGE_BUCKET / STORAGE_REGION
  • RESEND_API_KEY
  • EMAIL_FROM
  • NEXT_PUBLIC_APP_URL — your production URL

6. Push the database schema to production

After your first successful deploy, run:

DATABASE_URL=<neon-connection-string> pnpm db:push

Or connect to Neon directly and push via Drizzle Studio.

7. Update Google OAuth redirect URIs

In Google Cloud Console → OAuth credentials, add your production URL:

https://yourdomain.com/api/auth/callback/google

Set up Stripe

  1. Create a Stripe account
  2. Create your products and prices in the Stripe dashboard — one Price object per plan per interval (6 total: Starter monthly/annual, Pro monthly/annual, Business monthly/annual)
  3. Copy each Price ID and set the corresponding STRIPE_PRICE_ID_* env vars in Vercel
  4. Set STRIPE_SECRET_KEY to your production secret key (sk_live_...)
  5. Add a webhook endpoint in Stripe dashboard → Developers → Webhooks:
    • URL: https://yourdomain.com/api/webhooks/stripe
    • Events: customer.subscription.created, customer.subscription.updated, customer.subscription.deleted, invoice.payment_failed
  6. Copy the webhook signing secret and set STRIPE_WEBHOOK_SECRET in Vercel

CI pipeline with real credentials

The CI workflow ships with dummy env values so lint, type-check, and tests pass without any real credentials. If you want your CI build step to use real values, add them as GitHub repository secrets (Settings → Secrets and variables → Actions) and override the build step in .github/workflows/ci.yml:

- name: Build
  run: pnpm build
  env:
    RESEND_API_KEY: ${{ secrets.RESEND_API_KEY }}
    RESEND_FROM_EMAIL: ${{ secrets.RESEND_FROM_EMAIL }}
    B2_PUBLIC_URL: ${{ secrets.B2_PUBLIC_URL }}
    STRIPE_SECRET_KEY: ${{ secrets.STRIPE_SECRET_KEY }}
    STRIPE_WEBHOOK_SECRET: ${{ secrets.STRIPE_WEBHOOK_SECRET }}
    STRIPE_PRICE_ID_STARTER_MONTHLY: ${{ secrets.STRIPE_PRICE_ID_STARTER_MONTHLY }}
    STRIPE_PRICE_ID_STARTER_ANNUAL: ${{ secrets.STRIPE_PRICE_ID_STARTER_ANNUAL }}
    STRIPE_PRICE_ID_PRO_MONTHLY: ${{ secrets.STRIPE_PRICE_ID_PRO_MONTHLY }}
    STRIPE_PRICE_ID_PRO_ANNUAL: ${{ secrets.STRIPE_PRICE_ID_PRO_ANNUAL }}
    STRIPE_PRICE_ID_BUSINESS_MONTHLY: ${{ secrets.STRIPE_PRICE_ID_BUSINESS_MONTHLY }}
    STRIPE_PRICE_ID_BUSINESS_ANNUAL: ${{ secrets.STRIPE_PRICE_ID_BUSINESS_ANNUAL }}

On this page