Scalar LogoScalar

Deployment

Deploy your Scalar application to production

Platform Guides

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

  1. Go to vercel.com and sign in
  2. Click "New Project" and import your repository
  3. 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:

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

# 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

  1. Go to netlify.com and sign in
  2. Click "New site from Git" and connect your repository

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

netlify/functions/api.js
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

/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;
    }
}
# 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

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

docker-compose.yml
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

ecosystem.config.js
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

.env.production
# 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

healthcheck.js
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

lib/logger.js
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:

nginx.conf
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

middleware/rate-limit.js
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

lib/cache.js
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

  1. Database Connection Errors

    • Check DATABASE_URL format
    • Verify database server is running
    • Check firewall settings
  2. Build Failures

    • Clear node_modules and reinstall
    • Check Node.js version compatibility
    • Verify all environment variables are set
  3. Performance Issues

    • Enable caching
    • Optimize database queries
    • Use CDN for static assets
    • Monitor memory usage
  4. SSL Certificate Issues

    • Verify certificate validity
    • Check intermediate certificates
    • Ensure proper nginx configuration