PM2 Deployment Automation and CI/CD Integration

PM2 Deployment Automation and CI/CD Integration

This entry is part 6 of 7 in the series PM2 Mastery: From Zero to Production Hero

Mastering deployment automation is crucial for modern applications. This guide covers comprehensive PM2 deployment strategies, CI/CD integration, and advanced automation techniques for production environments.

Zero-Downtime Deployment with PM2

PM2’s reload feature enables zero-downtime deployments by gracefully restarting application instances one by one, ensuring continuous service availability.

// Graceful shutdown implementation
const express = require('express');
const app = express();

let isShuttingDown = false;

// Health check endpoint
app.get('/health', (req, res) => {
  if (isShuttingDown) {
    return res.status(503).json({ 
      status: 'shutting_down'
    });
  }
  res.json({ 
    status: 'healthy',
    uptime: process.uptime(),
    pid: process.pid
  });
});

// Graceful shutdown handler
const gracefulShutdown = (signal) => {
  console.log(`Received ${signal}, starting graceful shutdown...`);
  isShuttingDown = true;
  
  server.close((err) => {
    if (err) {
      console.error('Error during server shutdown:', err);
      process.exit(1);
    }
    console.log('HTTP server closed');
    process.exit(0);
  });
  
  // Force shutdown after 30 seconds
  setTimeout(() => {
    console.error('Could not close connections in time, forcefully shutting down');
    process.exit(1);
  }, 30000);
};

process.on('SIGTERM', () => gracefulShutdown('SIGTERM'));
process.on('SIGINT', () => gracefulShutdown('SIGINT'));

const server = app.listen(process.env.PORT || 3000);

Advanced Deployment Script

#!/bin/bash
# advanced-deploy.sh

APP_NAME="myapp"
HEALTH_URL="http://localhost:8000/health"
LOG_FILE="/var/log/myapp/deployment.log"

log() {
    echo "[$(date '+%Y-%m-%d %H:%M:%S')] $1" | tee -a $LOG_FILE
}

check_prerequisites() {
    log "Checking deployment prerequisites..."
    
    if ! pm2 list > /dev/null 2>&1; then
        log "ERROR: PM2 is not running"
        return 1
    fi
    
    if ! pm2 describe $APP_NAME > /dev/null 2>&1; then
        log "ERROR: Application '$APP_NAME' not found in PM2"
        return 1
    fi
    
    log "Prerequisites check completed"
    return 0
}

update_application() {
    log "Updating application code..."
    
    if ! git pull origin main; then
        log "ERROR: Failed to pull latest changes"
        return 1
    fi
    
    if ! npm ci --production; then
        log "ERROR: Failed to install dependencies"
        return 1
    fi
    
    log "Application update completed"
    return 0
}

perform_pm2_reload() {
    log "Performing PM2 reload..."
    
    if pm2 reload $APP_NAME --update-env; then
        log "PM2 reload completed successfully"
        return 0
    else
        log "ERROR: PM2 reload failed"
        return 1
    fi
}

check_health() {
    local retries=10
    local count=0
    
    log "Performing health checks..."
    
    while [ $count -lt $retries ]; do
        count=$((count + 1))
        
        if curl -f -s $HEALTH_URL > /dev/null 2>&1; then
            log "Health check passed"
            return 0
        fi
        
        log "Health check $count/$retries failed, retrying..."
        sleep 5
    done
    
    log "ERROR: Health checks failed"
    return 1
}

main() {
    log "Starting deployment process"
    
    if ! check_prerequisites; then
        exit 1
    fi
    
    if ! update_application; then
        exit 1
    fi
    
    if ! perform_pm2_reload; then
        exit 1
    fi
    
    if ! check_health; then
        exit 1
    fi
    
    log "Deployment completed successfully!"
}

main "$@"

GitHub Actions CI/CD Pipeline

# .github/workflows/deploy.yml
name: Deploy Application

on:
  push:
    branches: [ main ]

env:
  NODE_VERSION: '18'
  PM2_APP_NAME: 'myapp'

jobs:
  test:
    runs-on: ubuntu-latest
    steps:
    - name: Checkout code
      uses: actions/checkout@v4
    
    - name: Setup Node.js
      uses: actions/setup-node@v4
      with:
        node-version: ${{ env.NODE_VERSION }}
        cache: 'npm'
    
    - name: Install dependencies
      run: npm ci
    
    - name: Run tests
      run: npm test

  deploy:
    needs: test
    runs-on: ubuntu-latest
    environment: production
    
    steps:
    - name: Deploy to production
      uses: appleboy/ssh-action@v0.1.8
      with:
        host: ${{ secrets.PRODUCTION_HOST }}
        username: ${{ secrets.PRODUCTION_USERNAME }}
        key: ${{ secrets.PRODUCTION_SSH_KEY }}
        script: |
          cd /var/www/production
          git pull origin main
          npm ci --production
          pm2 reload myapp --update-env
          sleep 30
          
          # Health check
          if curl -f http://localhost:8000/health; then
            echo "Deployment successful"
            pm2 save
          else
            echo "Deployment failed"
            exit 1
          fi
graph TD
    A[Code Commit] --> B[CI/CD Trigger]
    B --> C[Run Tests]
    C --> D{Tests Pass?}
    D -->|No| E[Deployment Failed]
    D -->|Yes| F[Deploy to Server]
    F --> G[PM2 Reload]
    G --> H[Health Checks]
    H --> I{Health OK?}
    I -->|No| J[Rollback]
    I -->|Yes| K[Deployment Success]

Blue-Green Deployment

Blue-green deployment provides instant rollback capability by maintaining two identical environments.

#!/bin/bash
# blue-green-deploy.sh

BLUE_PORT=8000
GREEN_PORT=8001
NGINX_CONFIG="/etc/nginx/conf.d/myapp.conf"

get_current_environment() {
    if grep -q ":$BLUE_PORT" $NGINX_CONFIG; then
        echo "blue"
    else
        echo "green"
    fi
}

deploy_to_environment() {
    local env=$1
    local port=$2
    local app_dir="/var/www/myapp-$env"
    
    echo "Deploying to $env environment"
    
    cd $app_dir
    git pull origin main
    npm ci --production
    
    pm2 delete "myapp-$env" 2>/dev/null || true
    PORT=$port pm2 start ecosystem.config.js --name "myapp-$env"
    
    # Health check
    sleep 20
    if curl -f "http://localhost:$port/health"; then
        echo "$env environment is healthy"
        return 0
    else
        echo "$env environment health check failed"
        return 1
    fi
}

switch_traffic() {
    local target_env=$1
    local port=$2
    
    echo "Switching traffic to $target_env environment"
    
    # Update nginx configuration
    sed -i "s/proxy_pass http:\/\/localhost:[0-9]*;/proxy_pass http:\/\/localhost:$port;/" $NGINX_CONFIG
    
    # Reload nginx
    nginx -t && systemctl reload nginx
}

main() {
    current_env=$(get_current_environment)
    
    if [ "$current_env" = "blue" ]; then
        target_env="green"
        target_port=$GREEN_PORT
    else
        target_env="blue"
        target_port=$BLUE_PORT
    fi
    
    echo "Current: $current_env, Target: $target_env"
    
    if deploy_to_environment $target_env $target_port; then
        switch_traffic $target_env $target_port
        echo "Deployment successful"
    else
        echo "Deployment failed"
        exit 1
    fi
}

main

Monitoring During Deployment

#!/bin/bash
# deployment-monitor.sh

HEALTH_URL="http://localhost:8000/health"
MONITORING_DURATION=300  # 5 minutes

monitor_deployment() {
    local start_time=$(date +%s)
    local end_time=$((start_time + MONITORING_DURATION))
    local failure_count=0
    
    echo "Starting deployment monitoring..."
    
    while [ $(date +%s) -lt $end_time ]; do
        if curl -f -s $HEALTH_URL > /dev/null; then
            echo "Health check passed"
            failure_count=0
        else
            failure_count=$((failure_count + 1))
            echo "Health check failed ($failure_count)"
            
            if [ $failure_count -ge 3 ]; then
                echo "Too many failures, deployment monitoring failed"
                return 1
            fi
        fi
        
        sleep 10
    done
    
    echo "Deployment monitoring completed successfully"
    return 0
}

monitor_deployment

Production Best Practices

  • Zero-Downtime Deployments: Always use PM2 reload for seamless updates
  • Health Checks: Implement comprehensive health check endpoints
  • Rollback Strategy: Maintain previous versions for quick rollback
  • Monitoring: Set up alerts for deployment failures
  • Testing: Run automated tests before deployment
  • Gradual Rollout: Use blue-green or canary deployments for large changes

Series Complete! You now have comprehensive PM2 knowledge from basics to advanced deployment automation. This series covered fundamentals, configuration, clustering, systemd integration, monitoring, and deployment automation.

Navigate<< Advanced PM2 Monitoring, Logging, and Alerting Systems

Written by:

343 Posts

View All Posts
Follow Me :