LogoLaunchSaaS

Deployment

Deploy LaunchSaaS to production on Vercel, Cloudflare, Railway, or Docker. Step-by-step guides with environment setup and best practices.

Deployment

This guide covers deploying LaunchSaaS to production, including platform-specific instructions and best practices.

Pre-Deployment Checklist

Before deploying, ensure you have:

  • Database set up and accessible from production
  • All required environment variables configured
  • Database migrations applied (pnpm run db:migrate)
  • Admin user created (pnpm run init:scripts)
  • Stripe webhooks configured with production URL
  • Email domain verified in Resend
  • Social OAuth apps configured with production callback URLs
  • NEXT_PUBLIC_APP_ENV set to production
  • NEXT_PUBLIC_APP_URL set to your production domain

Vercel is the creators of Next.js and offers the best deployment experience.

Deploy via Dashboard

  1. Go to vercel.com
  2. Click "Add New Project"
  3. Import your Git repository
  4. Configure environment variables (add all from your .env file)
  5. Click "Deploy"

Deploy via CLI

  1. Install Vercel CLI:
pnpm install -g vercel
  1. Login and deploy:
vercel login
vercel --prod

Environment Variables

Add all required environment variables in Vercel Dashboard:

  1. Go to Project Settings → Environment Variables
  2. Add each variable for "Production" environment
  3. Redeploy after adding variables

Custom Domain

  1. Go to Project Settings → Domains
  2. Add your domain
  3. Configure DNS (CNAME to cname.vercel-dns.com or use Vercel nameservers)
  4. Wait for DNS propagation

Railway

Railway offers simple deployment with database included.

Deploy to Railway

  1. Go to railway.app
  2. Click "New Project"
  3. Select "Deploy from GitHub repo"
  4. Select your repository
  5. Add environment variables
  6. Click "Deploy"

Database on Railway

Railway can provision a PostgreSQL database:

  1. Click "New" → "Database" → "PostgreSQL"
  2. Copy the connection string
  3. Add as DATABASE_URL environment variable

Docker (VPS/Self-hosted)

Deploy on your own VPS or server using Docker. This method is ideal when you need full control over your infrastructure.

Recommended Setup: Use Docker for the application and a managed database service (Neon/Supabase) for simplicity and reliability.

Prerequisites

  • VPS or server with Docker installed
  • Managed database (Neon or Supabase recommended)
  • Domain name with DNS configured (optional)
  • At least 2GB RAM and 10GB disk space

Quick Start

  1. Clone and configure
git clone https://github.com/your-repo/launchsaas.git
cd launchsaas
cp .env.example .env
  1. Edit .env file

Configure the required variables:

  • DATABASE_URL - Your managed database connection string
  • BETTER_AUTH_SECRET - Random 32+ character string
  • ADMIN_EMAIL and ADMIN_PASSWORD - Admin credentials
  • NEXT_PUBLIC_APP_URL - Your production domain
  1. Build and start
./docker-quickstart.sh

Your application is now running at http://localhost:3000

Using Self-hosted Database (Optional)

If you prefer to run PostgreSQL in Docker instead of using a managed service:

  1. Uncomment database in docker-compose.yml
# Remove the # from these lines:
postgres:
  image: postgres:16-alpine
  # ... rest of config
volumes:
  postgres_data:
  1. Update .env with Docker database URL
DATABASE_URL=postgres://postgres:postgres@postgres:5432/launchsaas
  1. Restart containers
./docker-quickstart.sh down
./docker-quickstart.sh up

Self-hosted databases require regular backups and maintenance. Managed services like Neon or Supabase are recommended for production.

Production Setup

1. Configure Reverse Proxy & HTTPS

Choose one of the following options to add HTTPS support:

Option A: SWAG (Recommended for Docker)

SWAG is an all-in-one solution with automatic SSL certificate management via Let's Encrypt.

# Add to your docker-compose.yml
services:
  swag:
    image: lscr.io/linuxserver/swag:latest
    container_name: swag
    cap_add:
      - NET_ADMIN
    environment:
      - PUID=1000
      - PGID=1000
      - TZ=Asia/Shanghai
      - URL=yourdomain.com
      - VALIDATION=http
      - [email protected]
    volumes:
      - ./swag:/config
    ports:
      - 443:443
      - 80:80
    restart: unless-stopped

Create proxy configuration in ./swag/nginx/proxy-confs/launchsaas.subdomain.conf:

server {
    listen 443 ssl http2;
    server_name yourdomain.com;

    include /config/nginx/ssl.conf;

    location / {
        include /config/nginx/proxy.conf;
        proxy_pass http://launchsaas-app:3000;
    }
}

Option B: Caddy (Simplest)

Automatic HTTPS with minimal configuration:

# Add to docker-compose.yml
services:
  caddy:
    image: caddy:latest
    ports:
      - "80:80"
      - "443:443"
    volumes:
      - ./Caddyfile:/etc/caddy/Caddyfile
      - caddy_data:/data

Create Caddyfile:

yourdomain.com {
    reverse_proxy launchsaas-app:3000
}

Option C: Nginx

Manual SSL certificate configuration:

server {
    listen 80;
    server_name yourdomain.com;
    return 301 https://$server_name$request_uri;
}

server {
    listen 443 ssl http2;
    server_name yourdomain.com;

    ssl_certificate /path/to/cert.pem;
    ssl_certificate_key /path/to/key.pem;

    location / {
        proxy_pass http://localhost:3000;
        proxy_http_version 1.1;
        proxy_set_header Upgrade $http_upgrade;
        proxy_set_header Connection 'upgrade';
        proxy_set_header Host $host;
        proxy_set_header X-Real-IP $remote_addr;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
        proxy_set_header X-Forwarded-Proto $scheme;
    }
}

2. Environment Variables

Option A: Use separate environment files (Recommended)

Create different environment files for each environment:

# Create production environment file
cp .env.example .env.prod

# Edit .env.prod with production values
nano .env.prod

Configure production values:

  • APP_ENV=production
  • NEXT_PUBLIC_APP_URL=https://yourdomain.com
  • Production database URL
  • Production API keys for all services

Start with specific environment file:

# Using docker-quickstart.sh
./docker-quickstart.sh start --env .env.prod
./docker-quickstart.sh -e .env.staging

# Using docker compose directly
export ENV_FILE=.env.prod
docker compose up -d

Option B: Use single .env file

Update your existing .env with production values:

nano .env

Then start normally:

./docker-quickstart.sh

See Environment Variables for complete list.

3. Database Initialization

Run migrations and create admin user:

docker compose exec app pnpm run init

Or run separately:

docker compose exec app pnpm run db:migrate
docker compose exec app pnpm run init:scripts

Available Commands

./docker-quickstart.sh start    # Build and start (default)
./docker-quickstart.sh build    # Build Docker image only
./docker-quickstart.sh up       # Start containers only
./docker-quickstart.sh down     # Stop containers
./docker-quickstart.sh restart  # Restart containers
./docker-quickstart.sh logs     # View logs (Ctrl+C to exit)
./docker-quickstart.sh clean    # Remove containers and images

Using different environment files:

# Start with production environment
./docker-quickstart.sh start --env .env.prod

# Start with staging environment
./docker-quickstart.sh start -e .env.staging

# Build with specific environment
./docker-quickstart.sh build --env .env.prod

# View logs (no environment file needed)
./docker-quickstart.sh logs

Common workflows:

# Development
./docker-quickstart.sh

# Production
cp .env.example .env.prod
# Edit .env.prod with production values
./docker-quickstart.sh start --env .env.prod

# Switch environments
./docker-quickstart.sh down
./docker-quickstart.sh start --env .env.staging

# View logs and restart
./docker-quickstart.sh logs
./docker-quickstart.sh restart

Troubleshooting

Port already in use:

# Change port in .env
APP_PORT=3001

Then restart:

./docker-quickstart.sh restart

Database connection failed:

  • Verify DATABASE_URL is correct
  • Ensure database allows connections from Docker
  • For managed database, check IP whitelist settings
  • Test connection: docker compose exec app node -e "console.log(process.env.DATABASE_URL)"

Container won't start:

# Check error logs
./docker-quickstart.sh logs

# Check container status
docker compose ps

# Rebuild if needed
./docker-quickstart.sh clean
./docker-quickstart.sh build

Out of disk space:

# Clean up Docker resources
docker system prune -a

# Remove old containers and images
./docker-quickstart.sh clean

Why Vercel/Railway Instead?

While Docker gives you full control, managed platforms offer significant advantages:

  • ✅ Automatic SSL certificates
  • ✅ Zero-downtime deployments
  • ✅ Built-in CDN and edge caching
  • ✅ Automatic scaling
  • ✅ Free tier for small projects
  • ✅ No server maintenance

Use Docker when you need:

  • Full server control
  • Custom infrastructure requirements
  • Existing VPS/server to utilize
  • Compliance with specific hosting requirements
  • Integration with existing Docker-based infrastructure

Post-Deployment

Update Webhook URLs

After deployment, update your Stripe webhook URL:

  1. Go to Stripe Dashboard → Webhooks
  2. Update endpoint URL to: https://yourdomain.com/api/auth/stripe/webhook

Update OAuth Callback URLs

Update social login callback URLs:

  • GitHub: https://yourdomain.com/api/auth/callback/github
  • Google: https://yourdomain.com/api/auth/callback/google

Verify Deployment

  1. Visit your production URL
  2. Test sign up and sign in
  3. Test payment flow with Stripe test cards
  4. Verify emails are being sent
  5. Check admin panel access

Next Steps