GameCraftGameCraft

Deployment Guide

Deploy ProductReady to production - Vercel, Docker, environment setup, and CI/CD best practices

Deployment Guide

Complete guide to deploying ProductReady to production. Learn deployment strategies for Vercel, Docker, and self-hosted environments.

Recommended: Deploy to Vercel for the easiest experience. It's optimized for Next.js and requires minimal configuration.


Quick Overview

Deployment options:

  • Vercel - Recommended (1-click deploy, automatic HTTPS)
  • Docker - Self-hosted or cloud (AWS, GCP, Azure)
  • Kubernetes - For large-scale applications
  • VPS - Traditional server (DigitalOcean, Linode)

What you'll need:

  • PostgreSQL database (Neon, Supabase, or self-hosted)
  • Email provider (Resend, Postmark, AWS SES)
  • Payment provider (Stripe, LemonSqueezy) - optional
  • Domain name + SSL certificate

Deploy to Vercel

One-Click Deploy

The fastest way to get started:

Fork Repository

Fork ProductReady to your GitHub account

Import to Vercel

  1. Go to vercel.com/new
  2. Import your forked repository
  3. Select apps/productready as root directory

Configure Environment Variables

Add these in Vercel dashboard:

# Database
PG_DATABASE_URL=postgresql://user:pass@host:5432/db

# Auth
BETTER_AUTH_SECRET=your-32-char-secret
BETTER_AUTH_URL=https://your-domain.vercel.app

# OAuth (optional)
GITHUB_CLIENT_ID=your-github-client-id
GITHUB_CLIENT_SECRET=your-github-secret
GOOGLE_CLIENT_ID=your-google-client-id
GOOGLE_CLIENT_SECRET=your-google-secret

# Email (optional)
RESEND_API_KEY=re_123456789

# Payment (optional)
STRIPE_SECRET_KEY=sk_live_...
STRIPE_WEBHOOK_SECRET=whsec_...

Deploy

Click Deploy - Vercel will:

  • Install dependencies
  • Build your app
  • Deploy to production
  • Provide a URL

Custom Domain

Add domain in Vercel:

  • Go to Project Settings → Domains
  • Add your domain (e.g., productready.com)

Update DNS:

  • Add CNAME record pointing to cname.vercel-dns.com
  • Wait for DNS propagation (usually < 5 minutes)

Update environment variables:

BETTER_AUTH_URL=https://productready.com

Environment-Specific Variables

Vercel supports different environments:

# Production only
STRIPE_SECRET_KEY=sk_live_...

# Preview (PR deployments)
STRIPE_SECRET_KEY=sk_test_...

# Development
STRIPE_SECRET_KEY=sk_test_...

Deploy with Docker

Dockerfile

ProductReady includes a production-ready Dockerfile:

# apps/productready/Dockerfile
FROM node:20-alpine AS base

# Install dependencies only when needed
FROM base AS deps
RUN apk add --no-cache libc6-compat
WORKDIR /app

# Install pnpm
RUN npm install -g pnpm

# Copy package files
COPY package.json pnpm-lock.yaml ./
RUN pnpm install --frozen-lockfile

# Rebuild the source code only when needed
FROM base AS builder
WORKDIR /app
COPY --from=deps /app/node_modules ./node_modules
COPY . .

# Build
ENV NEXT_TELEMETRY_DISABLED 1
RUN pnpm build

# Production image
FROM base AS runner
WORKDIR /app

ENV NODE_ENV production
ENV NEXT_TELEMETRY_DISABLED 1

RUN addgroup --system --gid 1001 nodejs
RUN adduser --system --uid 1001 nextjs

# Copy built files
COPY --from=builder /app/public ./public
COPY --from=builder --chown=nextjs:nodejs /app/.next/standalone ./
COPY --from=builder --chown=nextjs:nodejs /app/.next/static ./.next/static

USER nextjs

EXPOSE 3000

ENV PORT 3000
ENV HOSTNAME "0.0.0.0"

CMD ["node", "server.js"]

Build & Run

# Build image
docker build -t productready:latest .

# Run container
docker run -p 3000:3000 \
  -e PG_DATABASE_URL="postgresql://..." \
  -e BETTER_AUTH_SECRET="your-secret" \
  -e BETTER_AUTH_URL="https://your-domain.com" \
  productready:latest

Docker Compose

For local development or simple deployments:

# docker-compose.yml
version: '3.9'

services:
  app:
    build: .
    ports:
      - '3000:3000'
    environment:
      - PG_DATABASE_URL=postgresql://postgres:postgres@db:5432/productready
      - BETTER_AUTH_SECRET=your-32-char-secret
      - BETTER_AUTH_URL=http://localhost:3000
    depends_on:
      - db

  db:
    image: postgres:16-alpine
    environment:
      - POSTGRES_DB=productready
      - POSTGRES_USER=postgres
      - POSTGRES_PASSWORD=postgres
    volumes:
      - postgres_data:/var/lib/postgresql/data
    ports:
      - '5432:5432'

volumes:
  postgres_data:

Run with:

docker-compose up -d

Deploy to Kubernetes

Kubernetes Manifests

# k8s/deployment.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
  name: productready
spec:
  replicas: 3
  selector:
    matchLabels:
      app: productready
  template:
    metadata:
      labels:
        app: productready
    spec:
      containers:
      - name: productready
        image: your-registry/productready:latest
        ports:
        - containerPort: 3000
        env:
        - name: PG_DATABASE_URL
          valueFrom:
            secretKeyRef:
              name: productready-secrets
              key: database-url
        - name: BETTER_AUTH_SECRET
          valueFrom:
            secretKeyRef:
              name: productready-secrets
              key: auth-secret
        resources:
          requests:
            memory: "256Mi"
            cpu: "250m"
          limits:
            memory: "512Mi"
            cpu: "500m"
---
apiVersion: v1
kind: Service
metadata:
  name: productready-service
spec:
  selector:
    app: productready
  ports:
  - protocol: TCP
    port: 80
    targetPort: 3000
  type: LoadBalancer

Secrets

# k8s/secrets.yaml
apiVersion: v1
kind: Secret
metadata:
  name: productready-secrets
type: Opaque
stringData:
  database-url: postgresql://user:pass@host:5432/db
  auth-secret: your-32-char-secret

Apply:

kubectl apply -f k8s/secrets.yaml
kubectl apply -f k8s/deployment.yaml

Database Setup

  1. Create account at neon.tech
  2. Create project
  3. Copy connection string
  4. Add to environment:
PG_DATABASE_URL=postgresql://user:pass@host.neon.tech:5432/neondb?sslmode=require

Why Neon?

  • ✅ Serverless PostgreSQL (scales to zero)
  • ✅ Generous free tier
  • ✅ Branch databases for development
  • ✅ Built-in connection pooling

Supabase

  1. Create project at supabase.com
  2. Get connection string from Settings → Database
  3. Use connection pooling string for production:
# Transaction mode (recommended for Drizzle)
PG_DATABASE_URL=postgresql://postgres.[ref]:[password]@aws-0-[region].pooler.supabase.com:6543/postgres

Local Database

For local development, ProductReady provides a Docker Compose configuration to quickly start all required database services.

Start Local Database with make db

Run from the project root directory (kapps/):

# Start database services
make db

This starts:

  • PostgreSQL 16 (with pgvector extension)

    • Port: 5432
    • User: bika
    • Password: bikabika
    • Database: bikadev
    • Connection string: postgresql://bika:bikabika@localhost:5432/bikadev
  • Redis Stack 7.2 (with Redis Insight)

    • Redis port: 6379
    • Redis Insight UI: http://localhost:8001
    • Data persistence: ./.data/redis
  • Typesense 27.1 (search engine)

    • Port: 8108
    • API Key: bikabika
    • Data persistence: ./.data/typesense

Common Database Management Commands

# Stop database services
make db-stop

# View service logs
make db-logs

# Run database migrations (all projects)
make db-migrate

# Generate Prisma/Drizzle clients
make db-generate

# Seed database with test data
make db-seed

# Reset database
make db-reset

Configure Environment Variables

In apps/productready/.env, configure the local database connection:

.env
# PostgreSQL
PG_DATABASE_URL="postgresql://bika:bikabika@localhost:5432/bikadev"

# Redis (if needed)
REDIS_URL="redis://localhost:6379"

# Typesense (if needed)
TYPESENSE_URL="http://localhost:8108"
TYPESENSE_API_KEY="bikabika"

Data Persistence: All data is stored in the ./.data/ directory, which is already in .gitignore and won't be committed to version control.

Note: Make sure you have Docker and Docker Compose installed. If not, visit Docker's website to download.

Self-Hosted PostgreSQL

# Docker
docker run -d \
  --name postgres \
  -e POSTGRES_PASSWORD=mysecretpassword \
  -e POSTGRES_DB=productready \
  -p 5432:5432 \
  -v postgres_data:/var/lib/postgresql/data \
  postgres:16-alpine

# Connection string
PG_DATABASE_URL=postgresql://postgres:mysecretpassword@localhost:5432/productready

Environment Variables

Required

# Database
PG_DATABASE_URL=postgresql://...

# Authentication
BETTER_AUTH_SECRET=your-32-char-random-string
BETTER_AUTH_URL=https://your-production-domain.com

Optional - OAuth

# GitHub OAuth
GITHUB_CLIENT_ID=your-github-client-id
GITHUB_CLIENT_SECRET=your-github-client-secret

# Google OAuth
GOOGLE_CLIENT_ID=your-google-client-id.apps.googleusercontent.com
GOOGLE_CLIENT_SECRET=your-google-client-secret

Optional - Email

# Resend
RESEND_API_KEY=re_123456789

# Or Postmark
POSTMARK_API_KEY=your-postmark-token

# Or AWS SES
AWS_ACCESS_KEY_ID=AKIAIOSFODNN7EXAMPLE
AWS_SECRET_ACCESS_KEY=wJalrXUtnFEMI/...
AWS_REGION=us-east-1

Optional - Payment

# Stripe
STRIPE_SECRET_KEY=sk_live_...
STRIPE_WEBHOOK_SECRET=whsec_...
NEXT_PUBLIC_STRIPE_PUBLISHABLE_KEY=pk_live_...

# Or LemonSqueezy
LEMONSQUEEZY_API_KEY=your-api-key
LEMONSQUEEZY_STORE_ID=your-store-id
LEMONSQUEEZY_WEBHOOK_SECRET=your-webhook-secret

Generate Secrets

# Generate 32-character secret
openssl rand -base64 32

# Or use Node.js
node -e "console.log(require('crypto').randomBytes(32).toString('base64'))"

Database Migrations

Production Migrations

Important: Run migrations before deploying new code to avoid errors.

Option 1: Manually

# Install dependencies
pnpm install

# Run migrations
pnpm db:migrate

Option 2: CI/CD (recommended)

# .github/workflows/deploy.yml
name: Deploy
on:
  push:
    branches: [main]

jobs:
  deploy:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v3
      - uses: pnpm/action-setup@v2
      - uses: actions/setup-node@v3
        with:
          node-version: 20
          cache: 'pnpm'
      
      # Run migrations
      - run: pnpm install
      - run: pnpm db:migrate
        env:
          PG_DATABASE_URL: ${{ secrets.PG_DATABASE_URL }}
      
      # Deploy (Vercel example)
      - run: npx vercel --prod
        env:
          VERCEL_TOKEN: ${{ secrets.VERCEL_TOKEN }}

SSL/HTTPS

Vercel

✅ Automatic HTTPS - no configuration needed

Docker/Self-Hosted

Use reverse proxy (nginx or Caddy):

Easiest option - automatic HTTPS:

# Caddyfile
productready.com {
  reverse_proxy localhost:3000
}

Run:

docker run -d \
  -p 80:80 \
  -p 443:443 \
  -v caddy_data:/data \
  -v $PWD/Caddyfile:/etc/caddy/Caddyfile \
  caddy:latest
# /etc/nginx/sites-available/productready
server {
  listen 80;
  server_name productready.com;
  return 301 https://$host$request_uri;
}

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

  ssl_certificate /etc/letsencrypt/live/productready.com/fullchain.pem;
  ssl_certificate_key /etc/letsencrypt/live/productready.com/privkey.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_cache_bypass $http_upgrade;
  }
}

Get SSL certificate:

# Install certbot
sudo apt install certbot python3-certbot-nginx

# Get certificate
sudo certbot --nginx -d productready.com -d www.productready.com

CI/CD Pipeline

GitHub Actions

# .github/workflows/ci.yml
name: CI/CD
on:
  push:
    branches: [main, develop]
  pull_request:
    branches: [main]

jobs:
  test:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v3
      - uses: pnpm/action-setup@v2
        with:
          version: 8
      - uses: actions/setup-node@v3
        with:
          node-version: 20
          cache: 'pnpm'
      
      - run: pnpm install
      - run: pnpm lint
      - run: pnpm typecheck
      - run: pnpm test

  deploy:
    needs: test
    if: github.ref == 'refs/heads/main'
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v3
      - uses: pnpm/action-setup@v2
      - uses: actions/setup-node@v3
        with:
          node-version: 20
          cache: 'pnpm'
      
      # Run migrations
      - run: pnpm install
      - run: pnpm db:migrate
        env:
          PG_DATABASE_URL: ${{ secrets.PG_DATABASE_URL }}
      
      # Deploy to Vercel
      - run: npx vercel deploy --prod --token=$VERCEL_TOKEN
        env:
          VERCEL_TOKEN: ${{ secrets.VERCEL_TOKEN }}
          VERCEL_ORG_ID: ${{ secrets.VERCEL_ORG_ID }}
          VERCEL_PROJECT_ID: ${{ secrets.VERCEL_PROJECT_ID }}

Health Checks

ProductReady includes a health check endpoint:

GET /api/health

Response:
{
  "status": "ok",
  "timestamp": "2025-11-19T10:00:00.000Z",
  "service": "productready",
  "version": "1.0.0",
  "buildNumber": "123",
  "buildSha": "a1b2c3d"
}

Uptime Monitoring

Use health check with monitoring services:

UptimeRobot:

  1. Add monitor
  2. URL: https://productready.com/api/health
  3. Interval: 5 minutes

Pingdom/StatusCake/Better Uptime:

  • Similar setup
  • Monitor /api/health endpoint

Performance Optimization

Enable Edge Runtime (Vercel)

For faster response times:

// src/app/api/*/route.ts
export const runtime = 'edge';

Database Connection Pooling

Use connection pooling for better performance:

# Neon (built-in pooling)
PG_DATABASE_URL=postgresql://user:pass@host.neon.tech:5432/db?sslmode=require

# Supabase (transaction mode)
PG_DATABASE_URL=postgresql://postgres.[ref]:[pass]@aws-0-[region].pooler.supabase.com:6543/postgres

# PgBouncer (self-hosted)
docker run -d \
  -p 6432:6432 \
  -e DATABASE_URL=postgresql://user:pass@postgres:5432/db \
  edoburu/pgbouncer

Caching

Enable Next.js caching:

// src/app/page.tsx
export const revalidate = 3600; // Revalidate every hour

// Or specific fetch requests
fetch('https://api.example.com/data', {
  next: { revalidate: 3600 }
});

Monitoring & Logging

Vercel Analytics

Enable in next.config.mjs:

const nextConfig = {
  experimental: {
    instrumentationHook: true,
  },
};

Sentry (Error Tracking)

pnpm add @sentry/nextjs
// sentry.client.config.ts
import * as Sentry from '@sentry/nextjs';

Sentry.init({
  dsn: process.env.NEXT_PUBLIC_SENTRY_DSN,
  tracesSampleRate: 1.0,
});

PostHog (Analytics)

pnpm add posthog-js
// src/lib/analytics.ts
import posthog from 'posthog-js';

if (typeof window !== 'undefined') {
  posthog.init(process.env.NEXT_PUBLIC_POSTHOG_KEY!, {
    api_host: 'https://app.posthog.com',
  });
}

Backup Strategy

Database Backups

Neon:

  • Automatic daily backups
  • Point-in-time recovery (PITR)
  • Configure in dashboard

Supabase:

  • Daily backups on paid plans
  • Manual backups via Dashboard → Database → Backups

Self-Hosted:

# Backup
pg_dump -h localhost -U postgres productready > backup.sql

# Restore
psql -h localhost -U postgres productready < backup.sql

# Automated (cron)
0 2 * * * pg_dump -h localhost -U postgres productready | gzip > /backups/productready-$(date +\%Y\%m\%d).sql.gz

Rollback Strategy

Vercel

  1. Go to Deployments
  2. Find previous working deployment
  3. Click "..." → "Promote to Production"

Docker

# Keep previous image
docker tag productready:latest productready:previous

# If new deployment fails, rollback
docker stop productready
docker rm productready
docker run -d --name productready productready:previous

Pre-Deployment Checklist

Before going to production:

  • Set all required environment variables
  • Run database migrations
  • Test with production database (staging environment)
  • Set up custom domain
  • Configure SSL/HTTPS
  • Set up error tracking (Sentry)
  • Set up uptime monitoring
  • Set up database backups
  • Test OAuth flows (production credentials)
  • Test payment webhooks (production mode)
  • Update BETTER_AUTH_URL to production domain
  • Update OAuth redirect URLs
  • Update Stripe/payment webhook URLs
  • Configure CSP headers (if needed)
  • Enable rate limiting (if needed)
  • Set up analytics

Post-Deployment

After deploying:

  • Monitor error rates (Sentry)
  • Check performance metrics (Vercel Analytics)
  • Verify webhooks are working
  • Test critical user flows
  • Monitor database performance
  • Check health endpoint
  • Update documentation
  • Notify team/users

Troubleshooting

Build Fails

# Check Node.js version
node --version # Should be 18+

# Clear cache and rebuild
rm -rf .next node_modules
pnpm install
pnpm build

Database Connection Fails

# Test connection
psql $PG_DATABASE_URL

# Check SSL mode
PG_DATABASE_URL=postgresql://...?sslmode=require

# Verify firewall/security groups allow connections

Webhooks Not Working

  1. Check webhook URL is publicly accessible
  2. Verify webhook secret is correct
  3. Check provider dashboard for delivery errors
  4. Use ngrok for testing: ngrok http 3000

Next Steps

  • Deploy to staging - Test everything before production
  • Set up monitoring - Track errors and performance
  • Configure backups - Protect your data
  • Plan scaling - Prepare for growth

Start with Vercel for the easiest deployment experience. Once you're comfortable, explore Docker/Kubernetes for more control.

On this page