PM2 Complete Guide | Production Process Manager for Node.js

PM2 Complete Guide | Production Process Manager for Node.js

이 글의 핵심

PM2 is a production process manager for Node.js. It provides clustering, monitoring, logging, zero-downtime reloads, and automatic restarts for reliable Node.js deployments.

Introduction

PM2 is a production-grade process manager for Node.js applications. It keeps your app running 24/7, handles crashes, enables clustering, and provides monitoring and logging.

Why PM2?

Without PM2:

node app.js
# App crashes → stays down
# Server restarts → app doesn't start
# 1 process → can't use all CPU cores

With PM2:

pm2 start app.js
# App crashes → auto-restart
# Server restarts → app auto-starts
# Cluster mode → uses all CPU cores
# Built-in monitoring and logs

1. Installation

# Global install (recommended)
npm install -g pm2

# Or with yarn
yarn global add pm2

# Verify
pm2 --version

2. Basic Usage

# Start app
pm2 start app.js

# Start with name
pm2 start app.js --name my-app

# Start with watch (auto-reload on file change)
pm2 start app.js --watch

# Start with environment
pm2 start app.js --env production

# List all processes
pm2 list

# Stop app
pm2 stop my-app

# Restart app
pm2 restart my-app

# Delete app from PM2
pm2 delete my-app

# Stop all
pm2 stop all

# Restart all
pm2 restart all

# Delete all
pm2 delete all

3. Cluster Mode

# Start in cluster mode (uses all CPU cores)
pm2 start app.js -i max

# Start with specific number of instances
pm2 start app.js -i 4

# Scale up/down
pm2 scale my-app 8   # Scale to 8 instances
pm2 scale my-app +2  # Add 2 more instances
pm2 scale my-app -2  # Remove 2 instances

Example: Cluster-Ready Express App

// app.js
const express = require('express');
const app = express();

app.get('/', (req, res) => {
  res.send(`Hello from process ${process.pid}`);
});

const PORT = process.env.PORT || 3000;
app.listen(PORT, () => {
  console.log(`Server running on port ${PORT}, PID: ${process.pid}`);
});
# Start 4 instances
pm2 start app.js -i 4

# Each request handled by different process
curl http://localhost:3000  # Process 1234
curl http://localhost:3000  # Process 1235
curl http://localhost:3000  # Process 1236
curl http://localhost:3000  # Process 1237

4. Ecosystem File

# Generate ecosystem file
pm2 ecosystem
// ecosystem.config.js
module.exports = {
  apps: [
    {
      name: 'api',
      script: './app.js',
      instances: 'max',
      exec_mode: 'cluster',
      env: {
        NODE_ENV: 'development',
        PORT: 3000,
      },
      env_production: {
        NODE_ENV: 'production',
        PORT: 8080,
      },
      error_file: './logs/err.log',
      out_file: './logs/out.log',
      log_date_format: 'YYYY-MM-DD HH:mm:ss Z',
      max_memory_restart: '500M',
      watch: false,
      ignore_watch: ['node_modules', 'logs'],
    },
    {
      name: 'worker',
      script: './worker.js',
      instances: 2,
      exec_mode: 'cluster',
      cron_restart: '0 0 * * *', // Restart at midnight
    },
  ],
};
# Start from ecosystem file
pm2 start ecosystem.config.js

# Start with specific environment
pm2 start ecosystem.config.js --env production

# Restart all apps in ecosystem
pm2 restart ecosystem.config.js

# Stop all apps in ecosystem
pm2 stop ecosystem.config.js

5. Monitoring

# Real-time monitoring
pm2 monit

# Show detailed info
pm2 show my-app

# Show logs
pm2 logs

# Show logs for specific app
pm2 logs my-app

# Show only errors
pm2 logs --err

# Flush all logs
pm2 flush

# Dashboard (web UI)
pm2 plus

6. Logging

# View logs
pm2 logs

# View last 100 lines
pm2 logs --lines 100

# Real-time logs
pm2 logs --raw

# JSON logs
pm2 logs --json

# Log file locations
~/.pm2/logs/app-out.log    # stdout
~/.pm2/logs/app-error.log  # stderr

Custom Log Files

// ecosystem.config.js
module.exports = {
  apps: [{
    name: 'api',
    script: './app.js',
    error_file: './logs/api-error.log',
    out_file: './logs/api-out.log',
    merge_logs: true,
    log_date_format: 'YYYY-MM-DD HH:mm:ss',
  }],
};

Log Rotation

# Install PM2 log rotate
pm2 install pm2-logrotate

# Configure rotation
pm2 set pm2-logrotate:max_size 10M        # Max size per file
pm2 set pm2-logrotate:retain 7            # Keep 7 files
pm2 set pm2-logrotate:compress true       # Compress old logs
pm2 set pm2-logrotate:rotateInterval '0 0 * * *'  # Rotate daily

7. Auto-Restart on Server Reboot

# Save current PM2 process list
pm2 save

# Generate startup script
pm2 startup

# Follow instructions to run the generated command
# Example: sudo env PATH=$PATH:/usr/bin pm2 startup systemd -u user --hp /home/user

# Now PM2 will auto-start on server reboot

# To remove startup script
pm2 unstartup

8. Zero-Downtime Reload

# Reload app (zero downtime, cluster mode only)
pm2 reload my-app

# Graceful reload (waits for existing requests)
pm2 reload my-app --wait-ready

# Restart (downtime)
pm2 restart my-app

Graceful Shutdown

// app.js
const express = require('express');
const app = express();

const server = app.listen(3000);

// Listen for shutdown signal
process.on('SIGINT', () => {
  console.log('Received SIGINT, graceful shutdown...');
  
  server.close(() => {
    console.log('Closed all connections');
    process.exit(0);
  });
  
  // Force shutdown after 30s
  setTimeout(() => {
    console.error('Forced shutdown');
    process.exit(1);
  }, 30000);
});

9. Environment Variables

# Set environment variables
pm2 start app.js --env production

# Or via ecosystem file
pm2 start ecosystem.config.js --env production
// ecosystem.config.js
module.exports = {
  apps: [{
    name: 'api',
    script: './app.js',
    env: {
      NODE_ENV: 'development',
      PORT: 3000,
      DB_HOST: 'localhost',
    },
    env_production: {
      NODE_ENV: 'production',
      PORT: 8080,
      DB_HOST: 'prod-db.example.com',
    },
    env_staging: {
      NODE_ENV: 'staging',
      PORT: 8080,
      DB_HOST: 'staging-db.example.com',
    },
  }],
};

10. TypeScript Support

# Install ts-node
npm install -D ts-node typescript

# Start TypeScript app
pm2 start app.ts --interpreter ts-node

# Or compile first (recommended for production)
npm run build
pm2 start dist/app.js
// ecosystem.config.js for TypeScript
module.exports = {
  apps: [{
    name: 'api',
    script: './src/app.ts',
    interpreter: 'ts-node',
    watch: ['src'],
    ignore_watch: ['node_modules', 'dist'],
    env: {
      TS_NODE_PROJECT: './tsconfig.json',
    },
  }],
};

11. Memory Management

// ecosystem.config.js
module.exports = {
  apps: [{
    name: 'api',
    script: './app.js',
    max_memory_restart: '500M', // Restart if memory exceeds 500MB
    max_restarts: 10,           // Max restarts within restart_delay
    min_uptime: '10s',          // Min uptime before considered stable
    restart_delay: 4000,        // Delay between restarts (ms)
  }],
};

12. Monitoring with PM2 Plus

# Link to PM2 Plus (free tier available)
pm2 plus

# Or with specific bucket
pm2 link <secret_key> <public_key>

# Disconnect
pm2 unlink

PM2 Plus provides:

  • Real-time dashboard
  • Error tracking
  • Transaction tracing
  • Custom metrics
  • Email/Slack alerts

13. Docker Integration

# Dockerfile
FROM node:18-alpine

WORKDIR /app

COPY package*.json ./
RUN npm ci --only=production

COPY . .

RUN npm install -g pm2

EXPOSE 3000

CMD ["pm2-runtime", "ecosystem.config.js"]
# docker-compose.yml
version: '3.8'
services:
  api:
    build: .
    ports:
      - "3000:3000"
    environment:
      - NODE_ENV=production
    restart: unless-stopped

14. Best Practices

1. Use Ecosystem File

# Good: version-controlled config
pm2 start ecosystem.config.js

# Bad: CLI flags (hard to reproduce)
pm2 start app.js -i 4 --max-memory-restart 500M --env production

2. Cluster Mode for Web Servers

module.exports = {
  apps: [{
    name: 'web-server',
    script: './app.js',
    instances: 'max',      // Use all CPUs
    exec_mode: 'cluster',  // Enable clustering
  }],
};

3. Fork Mode for Workers

module.exports = {
  apps: [{
    name: 'queue-worker',
    script: './worker.js',
    instances: 1,          // Single instance
    exec_mode: 'fork',     // Fork mode
  }],
};

4. Log Rotation

pm2 install pm2-logrotate
pm2 set pm2-logrotate:max_size 10M
pm2 set pm2-logrotate:retain 7

5. Startup Script

pm2 save
pm2 startup

15. Common Commands

# Status
pm2 status          # List all processes
pm2 list            # Same as status
pm2 show <app>      # Detailed info

# Logs
pm2 logs            # All logs
pm2 logs <app>      # App logs
pm2 logs --lines 200  # Last 200 lines
pm2 flush           # Clear logs

# Control
pm2 start <app>
pm2 stop <app>
pm2 restart <app>
pm2 reload <app>    # Zero-downtime (cluster only)
pm2 delete <app>

# Monitoring
pm2 monit           # Real-time monitor
pm2 plus            # Web dashboard

# Maintenance
pm2 save            # Save process list
pm2 startup         # Auto-start on boot
pm2 resurrect       # Restore saved processes
pm2 update          # Update PM2 in-memory
pm2 reset <app>     # Reset restart counter

16. Real-World Example

// ecosystem.config.js
module.exports = {
  apps: [
    // API server (cluster mode)
    {
      name: 'api',
      script: './dist/api/server.js',
      instances: 'max',
      exec_mode: 'cluster',
      env_production: {
        NODE_ENV: 'production',
        PORT: 8080,
        DB_HOST: process.env.DB_HOST,
        REDIS_URL: process.env.REDIS_URL,
      },
      error_file: './logs/api-error.log',
      out_file: './logs/api-out.log',
      max_memory_restart: '1G',
      min_uptime: '10s',
      max_restarts: 10,
    },
    
    // Background worker (fork mode)
    {
      name: 'worker',
      script: './dist/worker/index.js',
      instances: 2,
      exec_mode: 'fork',
      env_production: {
        NODE_ENV: 'production',
        REDIS_URL: process.env.REDIS_URL,
      },
      error_file: './logs/worker-error.log',
      out_file: './logs/worker-out.log',
      max_memory_restart: '500M',
      cron_restart: '0 3 * * *', // Restart at 3 AM daily
    },
    
    // Cron job
    {
      name: 'cleanup',
      script: './dist/jobs/cleanup.js',
      instances: 1,
      cron_restart: '0 0 * * *', // Run at midnight
      autorestart: false,
    },
  ],
};
# Deploy
npm run build
pm2 start ecosystem.config.js --env production
pm2 save
pm2 startup

# Update
git pull
npm run build
pm2 reload ecosystem.config.js --env production

Summary

PM2 is essential for Node.js production:

  • Process management with auto-restart
  • Cluster mode for multi-core scaling
  • Zero-downtime reloads
  • Monitoring and logging
  • Startup scripts for auto-start

Key Takeaways:

  1. Always use PM2 in production
  2. Enable cluster mode for web servers
  3. Configure log rotation
  4. Set up startup script
  5. Use ecosystem file for config

Next Steps:

  • Deploy with Docker
  • Reverse proxy with Nginx
  • Monitor with Winston

Resources: