Deployment
Deploy your SaaS application to any hosting platform.
Deploying a MakerKit application involves five steps: provision infrastructure (PostgreSQL database, S3-compatible storage, hosting), configure environment variables in your hosting platform, deploy your code, run Prisma migrations against the production database, and configure billing webhooks.
This process works on any platform that supports Next.js—Railway, Vercel, AWS, Cloudflare, or self-hosted Docker.
This guide covers the general deployment process. For platform-specific instructions, see the Railway or Docker guides.
Platform Selection
Choose Railway when: you want database + storage in one platform with minimal ops.
Choose Vercel when: you want rapid iteration and zero infrastructure management.
Choose Docker when: you need self-hosting, full control, or have compliance requirements.
Deployment Overview
Deploying your application involves these key steps:
- Provision infrastructure - Database, storage, and hosting
- Configure environment variables - Set production credentials
- Deploy the application - Push code to your hosting platform
- Run database migrations - Set up your database schema
- Configure webhooks - Connect your billing provider
Infrastructure Requirements
Your production environment needs:
| Component | Purpose | Options |
|---|---|---|
| Hosting | Run your Next.js app | Vercel, Railway, AWS, Cloudflare |
| Database | PostgreSQL database | Neon, PlanetScale, Supabase, Railway |
| Storage | File uploads | AWS S3, Cloudflare R2, Railway Storage |
| Transactional emails | Resend, Postmark, SendGrid |
Environment Variables
Every hosting platform requires you to set environment variables. Copy these from your Environment Setup:
Core Variables
DATABASE_URL=postgresql://...NEXT_PUBLIC_SITE_URL=https://your-domain.comBETTER_AUTH_SECRET=your-random-secret-stringNEXT_PUBLIC_BILLING_PROVIDER=stripeBETTER_AUTH_SECRET
Generate a secure random string (at least 32 characters) for production. Never reuse your development secret. You can generate one with: openssl rand -base64 32
Storage Variables
STORAGE_BASE_URL=https://...STORAGE_S3_ACCESS_KEY_ID=...STORAGE_S3_SECRET_ACCESS_KEY=...STORAGE_S3_BUCKET=...STORAGE_S3_REGION=...Billing Variables
For Stripe:
STRIPE_SECRET_KEY=sk_live_...STRIPE_WEBHOOK_SECRET=whsec_...For Polar:
POLAR_ACCESS_TOKEN=...Additional Environment Variables
If you configured additional settings using the Dev Tools during development (OAuth providers, email, feature flags, etc.), make sure to add those environment variables to your production environment as well. Refer to the Environment Setup guide for the complete list.
Build Configuration
For the monorepo structure, configure your hosting platform:
- Root Directory:
apps/web - Build Command:
pnpm build - Output Directory:
.next - Install Command:
pnpm install
Database Migrations
After your first deployment, run Prisma migrations to create the database schema:
read -s DATABASE_URL && export DATABASE_URL && npx prisma migrate deploy --schema packages/database/src/prisma/schema.prismaThis command:
- Prompts for your
DATABASE_URL(hidden input) - Exports it as an environment variable
- Runs
prisma migrate deployto apply existing migrations
migrate deploy vs migrate dev
Use prisma migrate deploy for production. It applies existing migrations without creating new ones. The prisma migrate dev command is for development only - it may prompt for interactive input and create new migration files.
Webhook Configuration
Set up webhooks so your billing provider can notify your app of subscription events:
| Provider | Webhook URL |
|---|---|
| Stripe | https://your-domain.com/api/billing/webhook |
| Polar | https://your-domain.com/api/billing/webhook |
Stripe Webhook Setup
- Go to Stripe Dashboard → Developers → Webhooks
- Click Add endpoint
- Enter your webhook URL:
https://your-domain.com/api/billing/webhook - Select events to listen to (at minimum:
checkout.session.completed,customer.subscription.updated,customer.subscription.deleted,invoice.paid) - Click Add endpoint
- Copy the Signing secret (starts with
whsec_) - Set
STRIPE_WEBHOOK_SECRET=whsec_...in your environment variables
Test vs Live Mode
Stripe has separate webhook endpoints for test and live mode. Make sure you're creating the webhook in Live mode for production.
Polar Webhook Setup
- Go to your Polar Dashboard → Settings → Webhooks
- Add your webhook URL:
https://your-domain.com/api/billing/webhook - Select the events you want to receive
- Save and copy the webhook secret to your environment variables
Post-Deployment Checklist
After deploying:
- [ ] Application loads without errors
- [ ] Database connection works
- [ ] Authentication flows work (sign up, sign in)
- [ ] Billing webhooks receive events
- [ ] File uploads work
- [ ] Emails send successfully
Platform Guides
For step-by-step instructions on specific platforms:
- Railway - All-in-one platform with built-in database and storage
- Docker - Self-host on any Docker-compatible platform
Troubleshooting
Build Failures
- Check that all environment variables are set
- Verify the root directory is
apps/web - Review build logs for specific errors
Database Connection Issues
- Confirm
DATABASE_URLis correct - Check if your database allows external connections
- Verify SSL requirements for your database provider
Missing Environment Variables
NEXT_PUBLIC_*variables are embedded at build time - redeploy after changes- Server-only variables are read at runtime
- Ensure no typos in variable names
Common Pitfalls
These issues cause most deployment failures:
Forgot to run migrations — The application deploys but crashes when accessing the database because tables don't exist. Always run
prisma migrate deployafter your first deployment and after adding new migrations.Webhook signature validation fails — Billing events aren't processed because the webhook secret is missing or wrong. Check that
STRIPE_WEBHOOK_SECRETmatches the signing secret from your Stripe Dashboard (not the API key).Cold starts cause webhook timeouts — On serverless platforms, the first request after idle time can be slow. If webhooks timeout, consider keeping your function warm or increasing the timeout limit.
CORS errors on API routes — If your frontend and API are on different domains, you may need to configure CORS headers. MakerKit handles same-domain requests, but custom setups may need adjustment.
Node.js version mismatch — The build works locally but fails on your hosting platform. Specify the Node.js version in your platform settings (Node.js 20+ recommended).
Monorepo root directory misconfigured — Build fails immediately because the platform can't find
package.json. Set the root directory toapps/webin your hosting platform settings.