Deployment
Deploy your Scalar application to production
Platform Guides
Vercel
Deploy Next.js apps with zero configuration
Netlify
Static site deployment with edge functions
Railway
Simple deployment with built-in database
DigitalOcean
VPS deployment with full control
AWS
Enterprise-grade cloud deployment
Docker
Containerized deployment anywhere
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
Perfect for Next.js applications with built-in optimizations.
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
Add these environment variables in the Vercel dashboard:
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
Create a vercel.json
file:
{
"buildCommand": "npm run build",
"outputDirectory": ".next",
"installCommand": "npm install",
"functions": {
"app/api/**": {
"maxDuration": 30
}
},
"crons": [
{
"path": "/api/cron/cleanup",
"schedule": "0 2 * * *"
}
]
}
Deploy
# Deploy via CLI
npm install -g vercel
vercel --prod
# Or push to main branch for automatic deployment
git push origin main
Vercel provides automatic SSL, global CDN, and serverless functions out of the box.
Netlify
Great for static sites and JAMstack applications.
Connect repository
- Go to netlify.com and sign in
- Click "New site from Git" and connect your repository
Configure build settings
[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
import { scalar } from '../../lib/scalar';
exports.handler = async (event, context) => {
// Handle API requests
const path = event.path.replace('/.netlify/functions/api', '');
return scalar.handleRequest({
method: event.httpMethod,
path,
headers: event.headers,
body: event.body,
});
};
Railway
Simple deployment with managed databases.
Install Railway CLI ```bash npm install -g @railway/cli railway login
</Step>
<Step>
### Initialize project ```bash railway init railway add postgresql ```
</Step>
<Step>
### Configure environment ```bash # Railway automatically provides
DATABASE_URL railway variables set SCALAR_SECRET=your-secret-key railway
variables set NODE_ENV=production ```
</Step>
<Step>### Deploy ```bash railway up ```</Step>
</Steps>
## DigitalOcean
Deploy on a VPS with full control.
<Steps>
<Step>
### Create a Droplet
1. Create a new Ubuntu 22.04 droplet
2. Connect via SSH
3. Update the system:
```bash
sudo apt update && sudo apt upgrade -y
Install dependencies
# Install Node.js
curl -fsSL https://deb.nodesource.com/setup_18.x | sudo -E bash -
sudo apt-get install -y nodejs
# Install PM2
sudo npm install -g pm2
# Install Nginx
sudo apt install nginx -y
# Install PostgreSQL
sudo apt install postgresql postgresql-contrib -y
Configure PostgreSQL
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
# Clone repository
git clone https://github.com/yourusername/your-scalar-app.git
cd your-scalar-app
# Install dependencies
npm install
# Set environment variables
cat << EOF > .env
DATABASE_URL="postgresql://scalar_user:secure_password@localhost:5432/scalar_production"
SCALAR_SECRET="your-production-secret"
NODE_ENV="production"
PORT=3000
EOF
# Build application
npm run build
# Start with PM2
pm2 start ecosystem.config.js
pm2 save
pm2 startup
Configure Nginx
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;
}
}
# Enable site
sudo ln -s /etc/nginx/sites-available/scalar /etc/nginx/sites-enabled/
sudo nginx -t
sudo systemctl reload nginx
# Install SSL certificate
sudo apt install certbot python3-certbot-nginx -y
sudo certbot --nginx -d your-domain.com
AWS
Enterprise deployment with auto-scaling and managed services.
Set up RDS Database 1. Go to AWS RDS console 2. Create new PostgreSQL
database 3. Configure security groups to allow connections 4. Note the endpoint and credentials
Create Elastic Beanstalk Application ```bash # Install EB CLI pip install
awsebcli # Initialize application eb init eb create production ```
Configure environment variables ```bash eb setenv \
DATABASE_URL="postgresql://username:password@your-rds-endpoint:5432/dbname"
SCALAR_SECRET="your-production-secret" \ NODE_ENV="production" ```
Configure auto-scaling ```yaml title=".ebextensions/autoscaling.config"
option_settings: aws:autoscaling:asg: MinSize: 2 MaxSize: 10 aws:autoscaling:trigger: MeasureName: CPUUtilization Unit: Percent UpperThreshold: 70 LowerThreshold: 30 aws:elasticbeanstalk:environment: LoadBalancerType: application ```
Docker
Containerized deployment for any platform.
Dockerfile
# Build stage
FROM node:18-alpine AS builder
WORKDIR /app
# Copy package files
COPY package*.json ./
RUN npm ci --only=production
# Copy source code
COPY . .
# Build application
RUN npm run build
# Production stage
FROM node:18-alpine AS production
WORKDIR /app
# Install dumb-init for proper signal handling
RUN apk add --no-cache dumb-init
# Create non-root user
RUN addgroup -g 1001 -S nodejs
RUN adduser -S scalar -u 1001
# Copy built application
COPY --from=builder --chown=scalar:nodejs /app ./
# Switch to non-root user
USER scalar
# Expose port
EXPOSE 3000
# Health check
HEALTHCHECK --interval=30s --timeout=3s --start-period=5s --retries=3 \
CMD node healthcheck.js
# Start application
ENTRYPOINT ["dumb-init", "--"]
CMD ["node", "server.js"]
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
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
Required Variables
# Database
DATABASE_URL="postgresql://user:pass@host:5432/db"
# Security
SCALAR_SECRET="your-very-secure-secret-key-32-chars-min"
JWT_SECRET="your-jwt-secret-key"
# Application
NODE_ENV="production"
PORT=3000
BASE_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=587
SMTP_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
Run migrations in production:
# Using the CLI
npx scalar migrate --env production
# Or programmatically
npm run db:migrate:prod
Monitoring and Logging
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
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
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
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
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;
Always test your deployment thoroughly in a staging environment before deploying to production.
Troubleshooting
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