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
- Create a new project in Neon
- Copy the connection string from the dashboard
- Set
DATABASE_URLin your Vercel env vars to the Neon connection string
2. Set up Backblaze B2
- Create a new bucket in B2 — set it to Public
- Create an App Key with read/write access to the bucket
- Note the endpoint, key ID, and app key
3. Set up Resend
- Create a Resend account and verify your sending domain
- Generate an API key
- Set
EMAIL_FROMto an address on your verified domain
4. Deploy to Vercel
npx vercelOr 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 stringBETTER_AUTH_SECRET— a new random secret for productionBETTER_AUTH_URL— your production URL (e.g.https://myapp.com)GOOGLE_CLIENT_ID/GOOGLE_CLIENT_SECRETSTORAGE_ENDPOINT— B2 endpointSTORAGE_ACCESS_KEY/STORAGE_SECRET_KEY/STORAGE_BUCKET/STORAGE_REGIONRESEND_API_KEYEMAIL_FROMNEXT_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:pushOr 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/googleSet up Stripe
- Create a Stripe account
- 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)
- Copy each Price ID and set the corresponding
STRIPE_PRICE_ID_*env vars in Vercel - Set
STRIPE_SECRET_KEYto your production secret key (sk_live_...) - 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
- URL:
- Copy the webhook signing secret and set
STRIPE_WEBHOOK_SECRETin 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 }}