Skip to content

Production Deployment GuideΒΆ

Complete guide for deploying the CyferWall Engage Backend to production environments.

πŸš€ Deployment OverviewΒΆ

Deployment TargetsΒΆ

  • Vercel (Primary) - Serverless deployment
  • Docker - Containerized deployment
  • Traditional VPS - Virtual private server

Deployment PipelineΒΆ

graph LR
    A[Code Push] --> B[GitHub Actions]
    B --> C[Tests & Linting]
    C --> D[Build & Bundle]
    D --> E[Deploy to Vercel]
    E --> F[Health Check]
    F --> G[Notification]

Vercel ConfigurationΒΆ

// vercel.json
{
  "version": 2,
  "name": "cyferwall-engage-backend",
  "builds": [
    {
      "src": "dist/server.js",
      "use": "@vercel/node"
    }
  ],
  "routes": [
    {
      "src": "/api/v1/(.*)",
      "dest": "/dist/server.js"
    },
    {
      "src": "/(.*)",
      "dest": "/dist/server.js"
    }
  ],
  "env": {
    "NODE_ENV": "production"
  },
  "functions": {
    "dist/server.js": {
      "maxDuration": 30
    }
  },
  "regions": ["iad1", "sfo1"],
  "includedFiles": [
    "dist/**",
    "node_modules/@prisma/client/**",
    "node_modules/.prisma/**",
    "prisma/**",
    "templates/**",
    "package.json"
  ]
}

Environment VariablesΒΆ

# Production environment variables
NODE_ENV=production
PORT=3000
API_VERSION=v1

# Database
DATABASE_URL=postgresql://user:pass@host:port/db

# Authentication  
JWT_SECRET=your-super-secure-jwt-secret-256-bits
AUTH_COOKIE_NAME=cyferwall-auth
AUTH_HEADER_NAME=x-cyferwall-auth

# External Services
RESEND_API_KEY=re_your_resend_api_key
RESEND_SENDER_ADDRESS=noreply@cyferwall.com

# Monitoring
DD_API_KEY=your_datadog_api_key
DD_ENV=production
DD_SERVICE=engage-backend
DD_VERSION=1.0.0

# Security
BCRYPT_SALT_ROUNDS=12
CORS_ORIGINS=https://cyferwall.com,https://*.cyferwall.com

Deployment CommandsΒΆ

# Install Vercel CLI
npm install -g vercel

# Login to Vercel
vercel login

# Deploy to production
vercel --prod

# Set environment variables
vercel env add NODE_ENV
vercel env add DATABASE_URL
vercel env add JWT_SECRET

🐳 Docker Deployment¢

DockerfileΒΆ

# Multi-stage build
FROM node:18-alpine AS builder

WORKDIR /app

# Copy package files
COPY package*.json ./
COPY prisma ./prisma/

# Install dependencies
RUN npm i  --only=production

# Generate Prisma client
RUN npx prisma generate

# Copy source code
COPY . .

# Build application
RUN npm run build

# Production image
FROM node:18-alpine AS production

WORKDIR /app

# Install dumb-init for signal handling
RUN apk add --no-cache dumb-init

# Create non-root user
RUN addgroup -g 1001 -S nodejs
RUN adduser -S nodeuser -u 1001

# Copy built application
COPY --from=builder --chown=nodeuser:nodejs /app/dist ./dist
COPY --from=builder --chown=nodeuser:nodejs /app/node_modules ./node_modules
COPY --from=builder --chown=nodeuser:nodejs /app/package.json ./
COPY --from=builder --chown=nodeuser:nodejs /app/prisma ./prisma
COPY --from=builder --chown=nodeuser:nodejs /app/templates ./templates

USER nodeuser

EXPOSE 3000

ENV NODE_ENV=production
ENV PORT=3000

ENTRYPOINT ["dumb-init", "--"]
CMD ["node", "dist/server.js"]

Docker Compose (Development)ΒΆ

version: '3.8'

services:
  app:
    build: .
    ports:
      - "3000:3000"
    environment:
      - NODE_ENV=production
      - DATABASE_URL=postgresql://postgres:password@db:5432/engage
      - JWT_SECRET=development-secret
    depends_on:
      - db
    volumes:
      - ./templates:/app/templates

  db:
    image: postgres:14-alpine
    environment:
      POSTGRES_DB: engage
      POSTGRES_USER: postgres
      POSTGRES_PASSWORD: password
    ports:
      - "5432:5432"
    volumes:
      - postgres_data:/var/lib/postgresql/data

volumes:
  postgres_data:

Docker Deployment CommandsΒΆ

# Build image
docker build -t cyferwall/engage-backend:latest .

# Run container
docker run -d \
  --name engage-backend \
  -p 3000:3000 \
  -e DATABASE_URL="postgresql://..." \
  -e JWT_SECRET="..." \
  cyferwall/engage-backend:latest

# Docker Compose
docker-compose up -d

# View logs
docker logs engage-backend -f

# Stop container
docker stop engage-backend

πŸ“¦ Build ProcessΒΆ

TypeScript CompilationΒΆ

# Build for production
npm run build

# Output structure
dist/
β”œβ”€β”€ server.js              # Main application entry
β”œβ”€β”€ routers/               # Route handlers
β”œβ”€β”€ util/                  # Utilities and helpers
β”œβ”€β”€ (common)/             # Common modules
└── *.js.map              # Source maps

Build ConfigurationΒΆ

// tsconfig.json (production)
{
  "compilerOptions": {
    "target": "ES2020",
    "module": "commonjs",
    "outDir": "./dist",
    "rootDir": "./src",
    "strict": true,
    "esModuleInterop": true,
    "skipLibCheck": true,
    "forceConsistentCasingInFileNames": true,
    "resolveJsonModule": true,
    "declaration": false,
    "sourceMap": true,
    "removeComments": true
  },
  "include": ["src/**/*"],
  "exclude": ["node_modules", "dist", "tests"]
}

Build OptimizationΒΆ

# Production build with optimizations
NODE_ENV=production npm run build

# Bundle analysis
npx webpack-bundle-analyzer dist/stats.json

# Tree shaking check
npx ts-unused-exports tsconfig.json

πŸ”§ Production ConfigurationΒΆ

Performance SettingsΒΆ

// Production server configuration
const productionConfig = {
  // Express settings
  trustProxy: true,
  compression: true,
  etag: false,

  // Database connection pool
  database: {
    connectionLimit: 50,
    idleTimeout: 30000,
    acquireTimeout: 60000
  },

  // Caching
  cache: {
    redis: process.env.REDIS_URL,
    ttl: 300 // 5 minutes
  },

  // Rate limiting
  rateLimit: {
    windowMs: 15 * 60 * 1000, // 15 minutes
    max: 1000 // requests per window
  }
}

Security HardeningΒΆ

// Security middleware
import helmet from 'helmet'
import rateLimit from 'express-rate-limit'

app.use(helmet({
  contentSecurityPolicy: {
    directives: {
      defaultSrc: ["'self'"],
      styleSrc: ["'self'", "'unsafe-inline'"],
      scriptSrc: ["'self'"],
      imgSrc: ["'self'", "data:", "https:"]
    }
  },
  hsts: {
    maxAge: 31536000,
    includeSubDomains: true,
    preload: true
  }
}))

app.use(rateLimit({
  windowMs: 15 * 60 * 1000,
  max: 1000,
  message: 'Too many requests from this IP'
}))

πŸ“Š Monitoring & ObservabilityΒΆ

Health ChecksΒΆ

// Comprehensive health check
app.get('/health', async (req, res) => {
  const checks = {
    status: 'healthy',
    timestamp: new Date().toISOString(),
    version: process.env.npm_package_version,
    environment: process.env.NODE_ENV,
    uptime: process.uptime(),
    checks: {
      database: await checkDatabase(),
      memory: checkMemory(),
      disk: await checkDisk(),
      externalServices: await checkExternalServices()
    }
  }

  const isHealthy = Object.values(checks.checks).every(check => check.status === 'healthy')

  res.status(isHealthy ? 200 : 503).json(checks)
})

Logging ConfigurationΒΆ

// Production logging with Winston
import winston from 'winston'

const logger = winston.createLogger({
  level: 'info',
  format: winston.format.combine(
    winston.format.timestamp(),
    winston.format.errors({ stack: true }),
    winston.format.json()
  ),
  defaultMeta: {
    service: 'engage-backend',
    environment: process.env.NODE_ENV
  },
  transports: [
    new winston.transports.File({ 
      filename: 'logs/error.log', 
      level: 'error' 
    }),
    new winston.transports.File({ 
      filename: 'logs/combined.log' 
    }),
    new winston.transports.Console({
      format: winston.format.simple()
    })
  ]
})

DataDog APMΒΆ

// DataDog tracing
import tracer from 'dd-trace'

tracer.init({
  service: 'engage-backend',
  env: process.env.NODE_ENV,
  version: process.env.npm_package_version,
  logInjection: true,
  runtimeMetrics: true
})

// Custom metrics
import StatsD from 'hot-shots'

const dogstatsd = new StatsD({
  hostname: 'localhost',
  port: 8125,
  prefix: 'engage.backend.'
})

// Track business metrics
dogstatsd.increment('cases.created')
dogstatsd.histogram('notifications.sent', duration)

πŸ”„ CI/CD PipelineΒΆ

GitHub Actions WorkflowΒΆ

# .github/workflows/deploy.yml
name: Deploy to Production

on:
  push:
    branches: [main]

jobs:
  test:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - uses: actions/setup-node@v4
        with:
          node-version: '18'
          cache: 'npm'

      - name: Install dependencies
        run: npm i 

      - name: Run tests
        run: npm test

      - name: Run linting
        run: npm run lint

      - name: Type checking
        run: npm run type-check

  deploy:
    needs: test
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4

      - name: Build application
        run: |
          npm i 
          npm run build

      - name: Deploy to Vercel
        uses: amondnet/vercel-action@v25
        with:
          vercel-token: ${{ secrets.VERCEL_TOKEN }}
          vercel-org-id: ${{ secrets.VERCEL_ORG_ID }}
          vercel-project-id: ${{ secrets.VERCEL_PROJECT_ID }}
          vercel-args: '--prod'

      - name: Run deployment tests
        run: |
          npm run test:deployment

      - name: Notify team
        uses: 8398a7/action-slack@v3
        with:
          status: ${{ job.status }}
          webhook_url: ${{ secrets.SLACK_WEBHOOK }}

πŸ—„οΈ Database MigrationΒΆ

Production Migration StrategyΒΆ

# Safe migration process
# 1. Backup database
pg_dump $DATABASE_URL > backup_$(date +%Y%m%d).sql

# 2. Run migrations
npx prisma migrate deploy

# 3. Verify migration
npx prisma migrate status

# 4. Generate new client
npx prisma generate

Zero-Downtime DeploymentΒΆ

// Blue-green deployment strategy
const deploymentStrategy = {
  // 1. Deploy to staging environment
  staging: 'https://staging-api.cyferwall.com',

  // 2. Run smoke tests
  smokeTests: [
    'GET /health',
    'POST /common/support/case',
    'GET /metrics'
  ],

  // 3. Switch traffic gradually
  trafficSplit: {
    blue: 0,    // Old version
    green: 100  // New version
  }
}

πŸ›‘οΈ Security ChecklistΒΆ

Pre-Deployment SecurityΒΆ

  • Environment variables secured
  • SSL/TLS certificates installed
  • Rate limiting configured
  • Input validation enabled
  • SQL injection protection
  • XSS prevention headers
  • CORS properly configured
  • Secrets rotation implemented

Post-Deployment VerificationΒΆ

  • Health checks passing
  • SSL certificate valid
  • API endpoints responding
  • Database connections working
  • Notifications sending
  • Monitoring alerts configured
  • Backup systems operational

🚨 Rollback Procedures¢

Automatic RollbackΒΆ

# Vercel automatic rollback
vercel rollback --timeout 300s

# Docker rollback
docker tag cyferwall/engage-backend:v1.0.0 cyferwall/engage-backend:latest
docker service update --image cyferwall/engage-backend:latest engage-backend

Manual RollbackΒΆ

# 1. Stop current deployment
vercel --prod --yes --confirm rollback

# 2. Restore database if needed
pg_restore -d $DATABASE_URL backup_20250123.sql

# 3. Verify rollback
curl https://api.cyferwall.com/health

πŸ“ž Support & TroubleshootingΒΆ

Production Support ContactsΒΆ

Common IssuesΒΆ

  1. Memory leaks: Monitor with process.memoryUsage()
  2. Database connections: Check connection pool status
  3. API timeouts: Review response times and optimize
  4. SSL certificate expiry: Set up auto-renewal

Emergency ProceduresΒΆ

  1. Service outage: Follow incident response playbook
  2. Data breach: Execute security incident protocol
  3. Performance degradation: Scale resources and investigate

πŸš€ Production deployment requires careful planning and monitoring. Always test thoroughly before deploying to production.