Deployment
Deploy your Init application to production with confidence. This guide covers deployment strategies, platform configurations, and best practices for going live.
Overview
Init is designed to deploy easily to modern hosting platforms with:
- Zero-downtime deployments - Rolling updates with no service interruption
- Environment-specific configurations - Separate settings for dev/staging/prod
- Database migrations - Automated schema updates
- Asset optimization - CDN integration and performance optimization
- Monitoring integration - Health checks and observability
Deployment Options
Vercel (Recommended)
Vercel provides the best developer experience for Next.js applications:
1. Initial Setup
# Install Vercel CLI
npm i -g vercel
# Login to Vercel
vercel login
# Deploy from project root
vercel
2. Project Configuration
Create vercel.json
in the project root:
{
"builds": [
{
"src": "apps/nextjs/package.json",
"use": "@vercel/next"
}
],
"routes": [
{
"src": "/(.*)",
"dest": "apps/nextjs/$1"
}
],
"installCommand": "pnpm install",
"buildCommand": "cd apps/nextjs && pnpm build",
"outputDirectory": "apps/nextjs/.next"
}
3. Environment Variables
Configure environment variables in the Vercel dashboard:
# Database
DATABASE_URL=your-production-database-url
# Supabase
NEXT_PUBLIC_SUPABASE_URL=your-supabase-url
NEXT_PUBLIC_SUPABASE_ANON_KEY=your-anon-key
SUPABASE_SERVICE_ROLE_KEY=your-service-role-key
# OpenAI (for AI features)
OPENAI_API_KEY=your-openai-key
# Stripe (for billing)
STRIPE_PUBLISHABLE_KEY=your-stripe-publishable-key
STRIPE_SECRET_KEY=your-stripe-secret-key
STRIPE_WEBHOOK_SECRET=your-webhook-secret
# NextAuth (if using)
NEXTAUTH_SECRET=your-nextauth-secret
NEXTAUTH_URL=https://your-domain.com
4. Custom Domain
- Add your domain in Vercel dashboard
- Update DNS records as instructed
- SSL certificate is automatically provisioned
Railway
Railway provides a simple deployment experience:
1. Connect Repository
- Connect your GitHub repository to Railway
- Select the Next.js template
- Configure build settings
2. Railway Configuration
Create railway.toml
:
[build]
builder = "nixpacks"
buildCommand = "pnpm build"
watchPatterns = ["**/*.tsx", "**/*.ts", "**/*.jsx", "**/*.js"]
[deploy]
startCommand = "cd apps/nextjs && pnpm start"
restartPolicyType = "on_failure"
restartPolicyMaxRetries = 10
3. Environment Variables
Set in Railway dashboard or use Railway CLI:
railway variables set DATABASE_URL=your-database-url
railway variables set NEXT_PUBLIC_SUPABASE_URL=your-supabase-url
Docker Deployment
For custom infrastructure or self-hosting:
1. Dockerfile
Create Dockerfile
in project root:
FROM node:18-alpine AS base
ENV PNPM_HOME="/pnpm"
ENV PATH="$PNPM_HOME:$PATH"
RUN corepack enable
# Install dependencies
FROM base AS deps
WORKDIR /app
COPY package.json pnpm-lock.yaml pnpm-workspace.yaml ./
COPY apps/nextjs/package.json ./apps/nextjs/
COPY packages/*/package.json ./packages/*/
RUN pnpm install --frozen-lockfile
# Build application
FROM base AS builder
WORKDIR /app
COPY . .
COPY --from=deps /app/node_modules ./node_modules
COPY --from=deps /app/apps/nextjs/node_modules ./apps/nextjs/node_modules
RUN pnpm build
# Production runtime
FROM base AS runner
WORKDIR /app
ENV NODE_ENV production
ENV PORT 3000
ENV HOSTNAME "0.0.0.0"
RUN addgroup --system --gid 1001 nodejs
RUN adduser --system --uid 1001 nextjs
COPY --from=builder /app/apps/nextjs/public ./apps/nextjs/public
COPY --from=builder --chown=nextjs:nodejs /app/apps/nextjs/.next/standalone ./
COPY --from=builder --chown=nextjs:nodejs /app/apps/nextjs/.next/static ./apps/nextjs/.next/static
USER nextjs
EXPOSE 3000
CMD ["node", "apps/nextjs/server.js"]
2. Docker Compose
Create docker-compose.prod.yml
:
version: "3.8"
services:
app:
build: .
ports:
- "3000:3000"
environment:
- DATABASE_URL=${DATABASE_URL}
- NEXT_PUBLIC_SUPABASE_URL=${NEXT_PUBLIC_SUPABASE_URL}
- NEXT_PUBLIC_SUPABASE_ANON_KEY=${NEXT_PUBLIC_SUPABASE_ANON_KEY}
- SUPABASE_SERVICE_ROLE_KEY=${SUPABASE_SERVICE_ROLE_KEY}
depends_on:
- postgres
restart: unless-stopped
postgres:
image: postgres:15
environment:
- POSTGRES_DB=init_prod
- POSTGRES_USER=postgres
- POSTGRES_PASSWORD=${DB_PASSWORD}
volumes:
- postgres_data:/var/lib/postgresql/data
ports:
- "5432:5432"
volumes:
postgres_data:
AWS Amplify
Deploy to AWS with CDN and global distribution:
1. Amplify Configuration
Create amplify.yml
:
version: 1
applications:
- appRoot: apps/nextjs
frontend:
phases:
preBuild:
commands:
- npm install -g pnpm
- pnpm install
build:
commands:
- pnpm build
artifacts:
baseDirectory: .next
files:
- "**/*"
cache:
paths:
- node_modules/**/*
- .next/cache/**/*
2. Environment Variables
Configure in AWS Amplify console or CLI:
aws amplify put-app --app-id your-app-id --environment-variables DATABASE_URL=your-db-url
Database Deployment
Supabase Production
-
Create Production Project:
- Go to Supabase dashboard
- Create new project for production
- Note the connection details
-
Run Migrations:
# Set production database URL export DATABASE_URL="your-production-url" # Push schema to production pnpm db:push-remote
-
Configure RLS Policies:
-- Enable RLS on all tables ALTER TABLE teams ENABLE ROW LEVEL SECURITY; ALTER TABLE team_members ENABLE ROW LEVEL SECURITY; -- Create policies for team access CREATE POLICY "Users can view their teams" ON teams FOR SELECT USING ( id IN ( SELECT team_id FROM team_members WHERE user_id = auth.uid() ) );
Self-Hosted PostgreSQL
If using your own PostgreSQL:
# Create production database
createdb init_production
# Set connection string
export DATABASE_URL="postgresql://user:password@host:5432/init_production"
# Run migrations
pnpm db:push
CI/CD Pipeline
GitHub Actions
Create .github/workflows/deploy.yml
:
name: Deploy to Production
on:
push:
branches: [main]
jobs:
deploy:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Setup Node.js
uses: actions/setup-node@v4
with:
node-version: "18"
cache: "pnpm"
- name: Install pnpm
run: npm install -g pnpm
- name: Install dependencies
run: pnpm install --frozen-lockfile
- name: Run tests
run: pnpm test
- name: Type check
run: pnpm typecheck
- name: Build application
run: pnpm build
- name: Deploy to Vercel
uses: vercel/action@v25.0.1
with:
vercel-token: ${{ secrets.VERCEL_TOKEN }}
vercel-org-id: ${{ secrets.VERCEL_ORG_ID }}
vercel-project-id: ${{ secrets.VERCEL_PROJECT_ID }}
vercel-args: "--prod"
Database Migration Pipeline
migrate:
runs-on: ubuntu-latest
if: github.ref == 'refs/heads/main'
steps:
- uses: actions/checkout@v4
- name: Setup Node.js
uses: actions/setup-node@v4
with:
node-version: "18"
- name: Install pnpm
run: npm install -g pnpm
- name: Install dependencies
run: pnpm install --frozen-lockfile
- name: Run database migrations
env:
DATABASE_URL: ${{ secrets.DATABASE_URL }}
run: pnpm db:push-remote
Environment Configuration
Production Environment Variables
# App Configuration
NODE_ENV=production
NEXT_PUBLIC_APP_URL=https://your-domain.com
# Database
DATABASE_URL=your-production-database-url
# Supabase
NEXT_PUBLIC_SUPABASE_URL=https://your-project.supabase.co
NEXT_PUBLIC_SUPABASE_ANON_KEY=your-anon-key
SUPABASE_SERVICE_ROLE_KEY=your-service-role-key
# Authentication
NEXTAUTH_SECRET=your-production-secret
NEXTAUTH_URL=https://your-domain.com
# AI Services
OPENAI_API_KEY=your-openai-key
# Payment Processing
STRIPE_PUBLISHABLE_KEY=pk_live_...
STRIPE_SECRET_KEY=sk_live_...
STRIPE_WEBHOOK_SECRET=whsec_...
# Email (if using)
RESEND_API_KEY=your-resend-key
SMTP_FROM=noreply@your-domain.com
# Analytics (optional)
NEXT_PUBLIC_ANALYTICS_ID=your-analytics-id
# Monitoring
SENTRY_DSN=your-sentry-dsn
Environment Validation
Add runtime validation:
// lib/env.ts
import { z } from "zod";
const envSchema = z.object({
NODE_ENV: z.enum(["development", "production", "test"]),
DATABASE_URL: z.string().url(),
NEXT_PUBLIC_SUPABASE_URL: z.string().url(),
NEXT_PUBLIC_SUPABASE_ANON_KEY: z.string(),
SUPABASE_SERVICE_ROLE_KEY: z.string(),
OPENAI_API_KEY: z.string().optional(),
STRIPE_PUBLISHABLE_KEY: z.string().optional(),
STRIPE_SECRET_KEY: z.string().optional(),
});
export const env = envSchema.parse(process.env);
Performance Optimization
Next.js Optimization
// next.config.js
/** @type {import('next').NextConfig} */
const nextConfig = {
// Enable standalone output for Docker
output: "standalone",
// Optimize images
images: {
domains: ["your-cdn-domain.com"],
formats: ["image/webp", "image/avif"],
},
// Enable compression
compress: true,
// Optimize bundle
experimental: {
optimizeCss: true,
optimizePackageImports: ["@repo/ui"],
},
// Headers for caching
async headers() {
return [
{
source: "/static/(.*)",
headers: [
{
key: "Cache-Control",
value: "public, max-age=31536000, immutable",
},
],
},
];
},
};
module.exports = nextConfig;
Database Performance
-- Add indexes for common queries
CREATE INDEX idx_team_members_user_id ON team_members(user_id);
CREATE INDEX idx_team_members_team_id ON team_members(team_id);
CREATE INDEX idx_teams_slug ON teams(slug);
-- Optimize queries
ANALYZE;
Health Checks
Application Health Check
// app/api/health/route.ts
import { db } from "@repo/db/drizzle-client";
export async function GET() {
try {
// Check database connection
await db.execute("SELECT 1");
// Check external services
const checks = {
database: "ok",
timestamp: new Date().toISOString(),
status: "healthy",
};
return Response.json(checks);
} catch (error) {
return Response.json(
{ status: "unhealthy", error: error.message },
{ status: 500 },
);
}
}
Database Health Check
-- Create health check function
CREATE OR REPLACE FUNCTION health_check()
RETURNS json AS $$
BEGIN
RETURN json_build_object(
'status', 'healthy',
'timestamp', NOW(),
'version', version(),
'connections', (
SELECT count(*)
FROM pg_stat_activity
WHERE state = 'active'
)
);
END;
$$ LANGUAGE plpgsql;
Rollback Procedures
Vercel Rollback
# List deployments
vercel ls
# Rollback to specific deployment
vercel rollback [deployment-url]
Database Rollback
# Create backup before deployment
pg_dump $DATABASE_URL > backup_$(date +%Y%m%d_%H%M%S).sql
# Restore if needed
psql $DATABASE_URL < backup_20240101_120000.sql
Docker Rollback
# Tag images with versions
docker build -t myapp:v1.2.3 .
# Rollback to previous version
docker service update --image myapp:v1.2.2 myapp_service
Security Hardening
HTTPS Configuration
// next.config.js
const nextConfig = {
async headers() {
return [
{
source: "/(.*)",
headers: [
{
key: "Strict-Transport-Security",
value: "max-age=31536000; includeSubDomains",
},
{
key: "X-Frame-Options",
value: "DENY",
},
{
key: "X-Content-Type-Options",
value: "nosniff",
},
{
key: "Referrer-Policy",
value: "origin-when-cross-origin",
},
],
},
];
},
};
Environment Secrets
# Use secret management services
aws ssm put-parameter --name "/init/prod/database-url" --value "..." --type "SecureString"
# Or use environment-specific secret files
echo "DATABASE_URL=..." | base64 > .env.prod.encrypted
Monitoring Setup
Basic Monitoring
// lib/monitoring.ts
export function logDeployment() {
console.log({
event: "deployment",
version: process.env.npm_package_version,
timestamp: new Date().toISOString(),
env: process.env.NODE_ENV,
});
}
// Call in layout.tsx or app initialization
logDeployment();
Error Tracking
// lib/sentry.ts
import * as Sentry from "@sentry/nextjs";
if (process.env.NODE_ENV === "production") {
Sentry.init({
dsn: process.env.SENTRY_DSN,
environment: process.env.NODE_ENV,
});
}
Troubleshooting
Common Issues
Build failures:
# Clear cache and rebuild
pnpm clean
pnpm install
pnpm build
Database connection issues:
# Test connection
psql $DATABASE_URL -c "SELECT version();"
Memory issues:
// Increase Node.js memory limit
"scripts": {
"build": "NODE_OPTIONS='--max-old-space-size=4096' next build"
}
Cold start performance:
- Use Vercel Pro for reduced cold starts
- Implement database connection pooling
- Optimize bundle size with tree shaking
Next Steps
After deployment:
- Monitor performance - Set up monitoring and observability
- Production checklist - Review production best practices
- User analytics - Implement user analytics
- Growth strategies - Plan scaling and growth