Vercel
Deployment
Platform Guides
Section titled “Platform Guides”Netlify
Railway
DigitalOcean
AWS
Docker
Pre-deployment Checklist
Section titled “Pre-deployment Checklist”Before deploying to production, ensure you have:
- Set up a production database
- Configured environment variables
- Set up file storage (if using file uploads)
- Configured email service (if using auth)
- Set up SSL certificate
- Configured domain and DNS
- Set up monitoring and logging
- Tested the application thoroughly
Vercel
Section titled “Vercel”Perfect for Next.js applications with built-in optimizations.
-
Connect your repository
Section titled “Connect your repository”- Go to vercel.com and sign in
- Click “New Project” and import your repository
- Vercel will automatically detect it’s a Next.js project
-
Configure environment variables
Section titled “Configure environment variables”Add these environment variables in the Vercel dashboard:
Terminal window DATABASE_URL="your-production-database-url"SCALAR_SECRET="your-production-secret"NEXTAUTH_URL="https://your-domain.com"NEXTAUTH_SECRET="your-nextauth-secret" -
Configure build settings
Section titled “Configure build settings”Create a
vercel.json
file:vercel.json {"buildCommand": "npm run build","outputDirectory": ".next","installCommand": "npm install","functions": {"app/api/**": {"maxDuration": 30}},"crons": [{"path": "/api/cron/cleanup","schedule": "0 2 * * *"}]} -
Deploy
Section titled “Deploy”Terminal window # Deploy via CLInpm install -g vercelvercel --prod# Or push to main branch for automatic deploymentgit push origin main
Netlify
Section titled “Netlify”Great for static sites and JAMstack applications.
-
Connect repository
Section titled “Connect repository”- Go to netlify.com and sign in
- Click “New site from Git” and connect your repository
-
Configure build settings
Section titled “Configure build settings”netlify.toml [build]command = "npm run build"publish = "dist"[build.environment]NODE_VERSION = "18"[[redirects]]from = "/api/*"to = "/.netlify/functions/:splat"status = 200[[headers]]for = "/*"[headers.values]X-Frame-Options = "DENY"X-XSS-Protection = "1; mode=block"X-Content-Type-Options = "nosniff" -
Set up Netlify Functions
Section titled “Set up Netlify Functions”netlify/functions/api.js import { scalar } from '../../lib/scalar';exports.handler = async (event, context) => {// Handle API requestsconst path = event.path.replace('/.netlify/functions/api', '');return scalar.handleRequest({method: event.httpMethod,path,headers: event.headers,body: event.body,});};
Railway
Section titled “Railway”Simple deployment with managed databases.
-
Install Railway CLI
Section titled “Install Railway CLI”Terminal window npm install -g @railway/clirailway login -
Initialize project
Section titled “Initialize project”Terminal window railway initrailway add postgresql -
Configure environment
Section titled “Configure environment”Terminal window # Railway automatically provides DATABASE_URLrailway variables set SCALAR_SECRET=your-secret-keyrailway variables set NODE_ENV=production -
Deploy
Section titled “Deploy”Terminal window railway up
DigitalOcean
Section titled “DigitalOcean”Deploy on a VPS with full control.
-
Create a Droplet
Section titled “Create a Droplet”- Create a new Ubuntu 22.04 droplet
- Connect via SSH
- Update the system:
Terminal window sudo apt update && sudo apt upgrade -y -
Install dependencies
Section titled “Install dependencies”Terminal window # Install Node.jscurl -fsSL https://deb.nodesource.com/setup_18.x | sudo -E bash -sudo apt-get install -y nodejs# Install PM2sudo npm install -g pm2# Install Nginxsudo apt install nginx -y# Install PostgreSQLsudo apt install postgresql postgresql-contrib -y -
Configure PostgreSQL
Section titled “Configure PostgreSQL”Terminal window sudo -u postgres psql# In PostgreSQL shell:CREATE DATABASE scalar_production;CREATE USER scalar_user WITH PASSWORD 'secure_password';GRANT ALL PRIVILEGES ON DATABASE scalar_production TO scalar_user;\q -
Deploy application
Section titled “Deploy application”Terminal window # Clone repositorygit clone https://github.com/yourusername/your-scalar-app.gitcd your-scalar-app# Install dependenciesnpm install# Set environment variablescat << EOF > .envDATABASE_URL="postgresql://scalar_user:secure_password@localhost:5432/scalar_production"SCALAR_SECRET="your-production-secret"NODE_ENV="production"PORT=3000EOF# Build applicationnpm run build# Start with PM2pm2 start ecosystem.config.jspm2 savepm2 startup -
Configure Nginx
Section titled “Configure Nginx”/etc/nginx/sites-available/scalar server {listen 80;server_name your-domain.com;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;proxy_cache_bypass $http_upgrade;}}Terminal window # Enable sitesudo ln -s /etc/nginx/sites-available/scalar /etc/nginx/sites-enabled/sudo nginx -tsudo systemctl reload nginx# Install SSL certificatesudo apt install certbot python3-certbot-nginx -ysudo certbot --nginx -d your-domain.com
Enterprise deployment with auto-scaling and managed services.
-
Set up RDS Database
Section titled “Set up RDS Database”- Go to AWS RDS console
- Create new PostgreSQL database
- Configure security groups to allow connections
- Note the endpoint and credentials
-
Create Elastic Beanstalk Application
Section titled “Create Elastic Beanstalk Application”Terminal window # Install EB CLIpip install awsebcli# Initialize applicationeb initeb create production -
Configure environment variables
Section titled “Configure environment variables”Terminal window eb setenv \DATABASE_URL="postgresql://username:password@your-rds-endpoint:5432/dbname" \SCALAR_SECRET="your-production-secret" \NODE_ENV="production" -
Configure auto-scaling
Section titled “Configure auto-scaling”.ebextensions/autoscaling.config option_settings:aws:autoscaling:asg:MinSize: 2MaxSize: 10aws:autoscaling:trigger:MeasureName: CPUUtilizationUnit: PercentUpperThreshold: 70LowerThreshold: 30aws:elasticbeanstalk:environment:LoadBalancerType: application
Docker
Section titled “Docker”Containerized deployment for any platform.
Dockerfile
Section titled “Dockerfile”# Build stageFROM node:18-alpine AS builder
WORKDIR /app
# Copy package filesCOPY package*.json ./RUN npm ci --only=production
# Copy source codeCOPY . .
# Build applicationRUN npm run build
# Production stageFROM node:18-alpine AS production
WORKDIR /app
# Install dumb-init for proper signal handlingRUN apk add --no-cache dumb-init
# Create non-root userRUN addgroup -g 1001 -S nodejsRUN adduser -S scalar -u 1001
# Copy built applicationCOPY --from=builder --chown=scalar:nodejs /app ./
# Switch to non-root userUSER scalar
# Expose portEXPOSE 3000
# Health checkHEALTHCHECK --interval=30s --timeout=3s --start-period=5s --retries=3 \ CMD node healthcheck.js
# Start applicationENTRYPOINT ["dumb-init", "--"]CMD ["node", "server.js"]
Docker Compose
Section titled “Docker Compose”version: '3.8'
services: app: build: . ports: - '3000:3000' environment: - DATABASE_URL=postgresql://postgres:password@db:5432/scalar - SCALAR_SECRET=your-secret-key - NODE_ENV=production depends_on: - db - redis restart: unless-stopped
db: image: postgres:15-alpine environment: - POSTGRES_DB=scalar - POSTGRES_USER=postgres - POSTGRES_PASSWORD=password volumes: - postgres_data:/var/lib/postgresql/data restart: unless-stopped
redis: image: redis:7-alpine restart: unless-stopped
nginx: image: nginx:alpine ports: - '80:80' - '443:443' volumes: - ./nginx.conf:/etc/nginx/nginx.conf - ./ssl:/etc/nginx/ssl depends_on: - app restart: unless-stopped
volumes: postgres_data:
Production Configuration
Section titled “Production Configuration”module.exports = { apps: [ { name: 'scalar-app', script: './server.js', instances: 'max', exec_mode: 'cluster', env: { NODE_ENV: 'development', }, env_production: { NODE_ENV: 'production', PORT: 3000, }, error_file: './logs/err.log', out_file: './logs/out.log', log_file: './logs/combined.log', time: true, watch: false, max_memory_restart: '1G', node_args: '--max-old-space-size=1024', }, ],};
Environment Variables
Section titled “Environment Variables”Required Variables
Section titled “Required Variables”# DatabaseDATABASE_URL="postgresql://user:pass@host:5432/db"
# SecuritySCALAR_SECRET="your-very-secure-secret-key-32-chars-min"JWT_SECRET="your-jwt-secret-key"
# ApplicationNODE_ENV="production"PORT=3000BASE_URL="https://your-domain.com"
# Authentication (if using)NEXTAUTH_URL="https://your-domain.com"NEXTAUTH_SECRET="your-nextauth-secret"
# Email (if using)SMTP_HOST="smtp.gmail.com"SMTP_PORT=587SMTP_USER="your-email@gmail.com"SMTP_PASS="your-app-password"
# File Storage (if using AWS S3)AWS_ACCESS_KEY_ID="your-access-key"AWS_SECRET_ACCESS_KEY="your-secret-key"AWS_S3_BUCKET="your-bucket-name"AWS_REGION="us-east-1"
# Monitoring (optional)SENTRY_DSN="your-sentry-dsn"
Database Migration
Section titled “Database Migration”Run migrations in production:
# Using the CLInpx scalar migrate --env production
# Or programmaticallynpm run db:migrate:prod
Monitoring and Logging
Section titled “Monitoring and Logging”Health Check Endpoint
Section titled “Health Check Endpoint”const http = require('http');
const options = { hostname: 'localhost', port: process.env.PORT || 3000, path: '/api/health', method: 'GET', timeout: 2000,};
const req = http.request(options, (res) => { if (res.statusCode === 200) { process.exit(0); } else { process.exit(1); }});
req.on('error', () => { process.exit(1);});
req.on('timeout', () => { req.destroy(); process.exit(1);});
req.end();
Logging Configuration
Section titled “Logging Configuration”const winston = require('winston');
const logger = winston.createLogger({ level: process.env.LOG_LEVEL || 'info', format: winston.format.combine( winston.format.timestamp(), winston.format.errors({ stack: true }), winston.format.json(), ), defaultMeta: { service: 'scalar-app' }, transports: [ new winston.transports.File({ filename: 'logs/error.log', level: 'error' }), new winston.transports.File({ filename: 'logs/combined.log' }), ],});
if (process.env.NODE_ENV !== 'production') { logger.add( new winston.transports.Console({ format: winston.format.simple(), }), );}
module.exports = logger;
Security Considerations
Section titled “Security Considerations”SSL/TLS
Section titled “SSL/TLS”Always use HTTPS in production:
server { listen 80; server_name your-domain.com; return 301 https://$server_name$request_uri;}
server { listen 443 ssl http2; server_name your-domain.com;
ssl_certificate /path/to/certificate.pem; ssl_certificate_key /path/to/private.key; ssl_protocols TLSv1.2 TLSv1.3; ssl_ciphers ECDHE-RSA-AES256-GCM-SHA512:DHE-RSA-AES256-GCM-SHA512;
# Security headers add_header X-Frame-Options "SAMEORIGIN" always; add_header X-XSS-Protection "1; mode=block" always; add_header X-Content-Type-Options "nosniff" always; add_header Referrer-Policy "no-referrer-when-downgrade" always; add_header Content-Security-Policy "default-src 'self'" always;
location / { proxy_pass http://localhost:3000; 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; }}
Rate Limiting
Section titled “Rate Limiting”const rateLimit = require('express-rate-limit');
const limiter = rateLimit({ windowMs: 15 * 60 * 1000, // 15 minutes max: 100, // limit each IP to 100 requests per windowMs message: 'Too many requests from this IP', standardHeaders: true, legacyHeaders: false,});
module.exports = limiter;
Performance Optimization
Section titled “Performance Optimization”Caching
Section titled “Caching”const Redis = require('redis');
const client = Redis.createClient({ url: process.env.REDIS_URL,});
client.on('error', (err) => console.log('Redis Client Error', err));
const cache = { async get(key) { try { const value = await client.get(key); return value ? JSON.parse(value) : null; } catch (error) { console.error('Cache get error:', error); return null; } },
async set(key, value, ttl = 3600) { try { await client.setEx(key, ttl, JSON.stringify(value)); } catch (error) { console.error('Cache set error:', error); } },
async del(key) { try { await client.del(key); } catch (error) { console.error('Cache delete error:', error); } },};
module.exports = cache;
Troubleshooting
Section titled “Troubleshooting”Common Issues
Section titled “Common Issues”-
Database Connection Errors
- Check DATABASE_URL format
- Verify database server is running
- Check firewall settings
-
Build Failures
- Clear node_modules and reinstall
- Check Node.js version compatibility
- Verify all environment variables are set
-
Performance Issues
- Enable caching
- Optimize database queries
- Use CDN for static assets
- Monitor memory usage
-
SSL Certificate Issues
- Verify certificate validity
- Check intermediate certificates
- Ensure proper nginx configuration