PM2 Configuration Mastery: Ecosystem Files and Environment Management

PM2 Configuration Mastery: Ecosystem Files and Environment Management

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

While starting applications with simple PM2 commands works for development, production environments demand sophisticated configuration management. This is where PM2’s ecosystem files shine, providing declarative configuration that makes application management scalable, maintainable, and consistent across different environments.

Understanding PM2 Ecosystem Files

An ecosystem file is a JavaScript or JSON configuration file that defines how PM2 should manage your applications. Think of it as a blueprint that describes every aspect of your application’s runtime environment.

graph TD
    A[ecosystem.config.js] --> B[Applications Array]
    B --> C[App 1 Config]
    B --> D[App 2 Config]
    B --> E[App N Config]
    
    C --> F[Basic Settings]
    C --> G[Environment Variables]
    C --> H[Advanced Options]
    
    F --> F1[name, script, cwd]
    G --> G1[env, env_production]
    H --> H1[instances, watch, logs]
    
    A --> I[Deploy Configuration]
    I --> J[Production Deploy]
    I --> K[Staging Deploy]

Basic Ecosystem File Structure

Let’s start with a comprehensive ecosystem.config.js template:

module.exports = {
  apps: [
    {
      // Basic application settings
      name: 'my-app',
      script: 'app.js',
      cwd: '/path/to/your/app',
      
      // Process management
      instances: 'max',
      exec_mode: 'cluster',
      
      // Environment variables
      env: {
        NODE_ENV: 'development',
        PORT: 3000,
        DATABASE_URL: 'mongodb://localhost:27017/myapp-dev'
      },
      env_production: {
        NODE_ENV: 'production',
        PORT: 8000,
        DATABASE_URL: 'mongodb://prod-server:27017/myapp'
      },
      
      // Logging
      log_file: './logs/combined.log',
      out_file: './logs/out.log',
      error_file: './logs/error.log',
      log_date_format: 'YYYY-MM-DD HH:mm:ss Z',
      
      // Advanced options
      watch: false,
      ignore_watch: ['node_modules', 'logs'],
      max_memory_restart: '1G',
      restart_delay: 4000,
      max_restarts: 10,
      min_uptime: '10s'
    }
  ]
};

Complete Configuration Options Reference

Understanding all available configuration options helps you fine-tune your applications for optimal performance:

mindmap
  root((PM2 Config))
    Basic
      name
      script
      cwd
      args
      interpreter
    Process
      instances
      exec_mode
      node_args
      restart_delay
    Environment
      env
      env_production
      env_staging
    Logging
      log_file
      out_file
      error_file
      log_date_format
      merge_logs
    Monitoring
      max_memory_restart
      max_restarts
      min_uptime
      autorestart
    Development
      watch
      ignore_watch
      watch_delay

Essential Configuration Options

module.exports = {
  apps: [{
    // === BASIC SETTINGS ===
    name: 'my-application',           // Application name in PM2
    script: './bin/www',              // Entry point script
    cwd: '/var/www/myapp',           // Working directory
    args: '--port 3000 --env prod',  // Command line arguments
    interpreter: 'node',              // Script interpreter
    interpreter_args: '--harmony',   // Interpreter arguments
    
    // === PROCESS MANAGEMENT ===
    instances: 4,                     // Number of instances
    exec_mode: 'cluster',            // 'fork' or 'cluster'
    restart_delay: 4000,             // Delay between restarts (ms)
    max_restarts: 10,                // Max restarts within min_uptime
    min_uptime: '10s',               // Min uptime before restart
    max_memory_restart: '1G',        // Restart if memory exceeds limit
    autorestart: true,               // Enable/disable auto restart
    
    // === ENVIRONMENT VARIABLES ===
    env: {
      NODE_ENV: 'development',
      PORT: 3000,
      DATABASE_URL: 'localhost:5432'
    },
    env_production: {
      NODE_ENV: 'production',
      PORT: 8000,
      DATABASE_URL: 'prod-db:5432'
    },
    
    // === LOGGING ===
    log_file: './logs/app.log',      // Combined logs
    out_file: './logs/out.log',      // STDOUT logs
    error_file: './logs/error.log',  // STDERR logs
    log_date_format: 'YYYY-MM-DD HH:mm:ss Z',
    merge_logs: true,                // Merge cluster logs
    
    // === DEVELOPMENT FEATURES ===
    watch: ['src', 'config'],        // Watch directories/files
    ignore_watch: [                  // Ignore patterns
      'node_modules',
      'logs',
      '*.log',
      'tmp'
    ],
    watch_delay: 1000,               // Watch debounce delay
    
    // === ADVANCED OPTIONS ===
    source_map_support: true,        // Enable source map support
    instance_var: 'INSTANCE_ID',     // Environment variable for instance ID
    pmx: true,                       // Enable PMX
    automation: false,               // Disable automation
    treekill: true,                  // Kill entire process tree
    kill_timeout: 1600,              // Timeout before force kill
    listen_timeout: 3000,            // Listen timeout
    shutdown_with_message: false     // Shutdown with SIGINT message
  }]
};

Multi-Environment Management

Managing different environments (development, staging, production) is crucial for modern applications. Here’s how to structure your ecosystem file for multiple environments:

Environment-Specific Configuration

module.exports = {
  apps: [{
    name: 'myapp',
    script: 'server.js',
    
    // Default environment (development)
    env: {
      NODE_ENV: 'development',
      PORT: 3000,
      DATABASE_URL: 'mongodb://localhost:27017/myapp-dev',
      REDIS_URL: 'redis://localhost:6379',
      LOG_LEVEL: 'debug',
      API_BASE_URL: 'http://localhost:3000/api',
      JWT_SECRET: 'dev-secret-key',
      MAIL_SERVICE: 'gmail',
      MAIL_USER: 'dev@example.com',
      MAIL_PASS: 'dev-password'
    },
    
    // Staging environment
    env_staging: {
      NODE_ENV: 'staging',
      PORT: 4000,
      DATABASE_URL: 'mongodb://staging-db:27017/myapp-staging',
      REDIS_URL: 'redis://staging-redis:6379',
      LOG_LEVEL: 'info',
      API_BASE_URL: 'https://staging-api.myapp.com/api',
      JWT_SECRET: 'staging-secret-key-longer',
      MAIL_SERVICE: 'sendgrid',
      MAIL_API_KEY: 'staging-sendgrid-key',
      instances: 2,
      max_memory_restart: '512M'
    },
    
    // Production environment
    env_production: {
      NODE_ENV: 'production',
      PORT: 8000,
      DATABASE_URL: 'mongodb://prod-cluster:27017/myapp',
      REDIS_URL: 'redis://prod-redis-cluster:6379',
      LOG_LEVEL: 'warn',
      API_BASE_URL: 'https://api.myapp.com/api',
      JWT_SECRET: process.env.JWT_SECRET, // From system environment
      MAIL_SERVICE: 'sendgrid',
      MAIL_API_KEY: process.env.SENDGRID_API_KEY,
      instances: 'max',
      max_memory_restart: '1G',
      node_args: '--max-old-space-size=2048'
    }
  }]
};
graph LR
    A[Ecosystem File] --> B[Development]
    A --> C[Staging]
    A --> D[Production]
    
    B --> B1[Local DB]
    B --> B2[Debug Logs]
    B --> B3[Single Instance]
    
    C --> C1[Staging DB]
    C --> C2[Info Logs]
    C --> C3[2 Instances]
    
    D --> D1[Production DB]
    D --> D2[Error Logs Only]
    D --> D3[Max Instances]
    
    E[Deploy Commands] --> F[pm2 start --env development]
    E --> G[pm2 start --env staging]
    E --> H[pm2 start --env production]

Using Environment-Specific Configurations

# Start with development environment (default)
pm2 start ecosystem.config.js

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

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

# Restart with different environment
pm2 restart myapp --env production

# Update environment variables without restart
pm2 reload myapp --env production

Managing Multiple Applications

Complex projects often involve multiple applications working together. Here’s how to manage them efficiently:

Multi-App Ecosystem Configuration

module.exports = {
  apps: [
    {
      // Main web application
      name: 'web-app',
      script: 'web/server.js',
      cwd: '/var/www/myproject',
      instances: 'max',
      exec_mode: 'cluster',
      env: {
        NODE_ENV: 'development',
        PORT: 3000,
        SERVICE_NAME: 'web'
      },
      env_production: {
        NODE_ENV: 'production',
        PORT: 8000,
        SERVICE_NAME: 'web'
      }
    },
    {
      // API microservice
      name: 'api-service',
      script: 'api/app.js',
      cwd: '/var/www/myproject',
      instances: 4,
      exec_mode: 'cluster',
      env: {
        NODE_ENV: 'development',
        PORT: 3001,
        SERVICE_NAME: 'api'
      },
      env_production: {
        NODE_ENV: 'production',
        PORT: 8001,
        SERVICE_NAME: 'api'
      }
    },
    {
      // Background worker
      name: 'worker',
      script: 'worker/index.js',
      cwd: '/var/www/myproject',
      instances: 2,
      exec_mode: 'fork',
      env: {
        NODE_ENV: 'development',
        WORKER_TYPE: 'background',
        QUEUE_URL: 'redis://localhost:6379'
      },
      env_production: {
        NODE_ENV: 'production',
        WORKER_TYPE: 'background',
        QUEUE_URL: 'redis://prod-redis:6379'
      }
    },
    {
      // Cron jobs
      name: 'scheduler',
      script: 'scheduler/cron.js',
      cwd: '/var/www/myproject',
      instances: 1,
      exec_mode: 'fork',
      cron_restart: '0 0 * * *', // Restart daily at midnight
      env: {
        NODE_ENV: 'development',
        SCHEDULER_ENABLED: 'true'
      },
      env_production: {
        NODE_ENV: 'production',
        SCHEDULER_ENABLED: 'true'
      }
    }
  ]
};

Application Grouping and Management

# Start all applications
pm2 start ecosystem.config.js

# Start specific application
pm2 start ecosystem.config.js --only web-app

# Start multiple specific applications
pm2 start ecosystem.config.js --only "web-app,api-service"

# Stop all applications
pm2 stop ecosystem.config.js

# Restart specific application with new environment
pm2 restart web-app --env production

# Scale specific application
pm2 scale api-service 6

# Monitor all applications
pm2 monit

Advanced Configuration Patterns

Let’s explore sophisticated configuration patterns for complex production scenarios:

Dynamic Configuration with Functions

const os = require('os');
const path = require('path');

// Calculate optimal instances based on CPU cores
const instances = process.env.NODE_ENV === 'production' 
  ? Math.max(os.cpus().length - 1, 1) 
  : 1;

// Dynamic log paths based on environment
const getLogPath = (env, type) => {
  const logDir = env === 'production' ? '/var/log/myapp' : './logs';
  return path.join(logDir, `${type}.log`);
};

module.exports = {
  apps: [{
    name: 'dynamic-app',
    script: 'app.js',
    instances: instances,
    
    // Dynamic environment configuration
    env: {
      NODE_ENV: 'development',
      PORT: 3000,
      INSTANCES: instances,
      LOG_LEVEL: 'debug'
    },
    
    env_production: {
      NODE_ENV: 'production',
      PORT: process.env.PORT || 8000,
      INSTANCES: instances,
      LOG_LEVEL: 'error',
      // Use system environment variables
      DATABASE_URL: process.env.DATABASE_URL,
      REDIS_URL: process.env.REDIS_URL,
      JWT_SECRET: process.env.JWT_SECRET
    },
    
    // Dynamic log configuration
    log_file: getLogPath(process.env.NODE_ENV, 'combined'),
    out_file: getLogPath(process.env.NODE_ENV, 'out'),
    error_file: getLogPath(process.env.NODE_ENV, 'error'),
    
    // Conditional watching
    watch: process.env.NODE_ENV === 'development',
    
    // Environment-specific memory limits
    max_memory_restart: process.env.NODE_ENV === 'production' ? '1G' : '500M'
  }]
};

Configuration Validation

// ecosystem.config.js with validation
const requiredEnvVars = ['DATABASE_URL', 'JWT_SECRET', 'REDIS_URL'];

// Validate required environment variables for production
if (process.env.NODE_ENV === 'production') {
  const missing = requiredEnvVars.filter(key => !process.env[key]);
  if (missing.length > 0) {
    console.error('Missing required environment variables:', missing);
    process.exit(1);
  }
}

// Validate configuration function
const validateConfig = (config) => {
  if (!config.name || !config.script) {
    throw new Error('App name and script are required');
  }
  
  if (config.instances && config.instances < 1) {
    throw new Error('Instances must be >= 1');
  }
  
  return true;
};

const appConfig = {
  name: 'validated-app',
  script: 'app.js',
  instances: parseInt(process.env.INSTANCES) || 'max',
  env_production: {
    NODE_ENV: 'production',
    DATABASE_URL: process.env.DATABASE_URL,
    JWT_SECRET: process.env.JWT_SECRET,
    REDIS_URL: process.env.REDIS_URL
  }
};

// Validate before export
validateConfig(appConfig);

module.exports = {
  apps: [appConfig]
};

Working with Ecosystem Files

Here are the essential commands for working with ecosystem files:

flowchart TD
    A[Create ecosystem.config.js] --> B[pm2 ecosystem]
    A --> C[Manual Creation]
    
    B --> D[Generated Template]
    C --> E[Custom Configuration]
    
    D --> F[pm2 start ecosystem.config.js]
    E --> F
    
    F --> G{Environment?}
    G -->|Development| H[Default env]
    G -->|Staging| I[--env staging]
    G -->|Production| J[--env production]
    
    H --> K[pm2 list]
    I --> K
    J --> K
    
    K --> L[Monitor & Manage]

Ecosystem File Commands

# Generate a sample ecosystem file
pm2 ecosystem

# Start applications from ecosystem file
pm2 start ecosystem.config.js

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

# Start only specific apps
pm2 start ecosystem.config.js --only web-app

# Reload all apps from ecosystem
pm2 reload ecosystem.config.js

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

# Delete all apps from ecosystem
pm2 delete ecosystem.config.js

# Validate ecosystem file (dry run)
pm2 start ecosystem.config.js --dry-run

Best Practices for Ecosystem Files

  • Version Control: Always commit ecosystem files to your repository
  • Environment Separation: Use clear environment naming conventions
  • Secret Management: Never hardcode secrets; use environment variables
  • Documentation: Comment your configuration for team members
  • Validation: Implement configuration validation for production
  • Backup: Keep backup copies of working configurations

Configuration Testing and Debugging

Testing your ecosystem configuration is crucial before deploying to production:

# Test ecosystem file syntax
node -c ecosystem.config.js

# Dry run to validate configuration
pm2 start ecosystem.config.js --dry-run

# Start in development mode for testing
NODE_ENV=development pm2 start ecosystem.config.js

# Check application status
pm2 list
pm2 describe myapp

# Test environment variable loading
pm2 exec myapp -- node -e "console.log(process.env.NODE_ENV)"

# Monitor resource usage
pm2 monit

# View configuration
pm2 show myapp

What’s Next?

You now have comprehensive knowledge of PM2 configuration management. You can create sophisticated ecosystem files, manage multiple environments, and handle complex multi-application setups. In the next part of this series, we’ll explore PM2’s clustering and performance optimization capabilities:

  • Understanding PM2 clustering and load balancing
  • CPU optimization and core utilization strategies
  • Memory management and leak prevention
  • Performance tuning and benchmarking techniques
  • Resource limits and health monitoring

The configuration patterns you’ve learned here will be essential as we dive into performance optimization in the next article.

Series Navigation:
← Part 1: PM2 Fundamentals
→ Part 2: Configuration Mastery (You are here)
→ Part 3: Clustering and Performance (Coming next)

Navigate<< EzPM2GUI: A Beautiful Web Interface for PM2 Process Management<< PM2 Fundamentals: Complete Installation and Setup Guide for Ubuntu ServerPM2 Clustering and Performance Optimization on Ubuntu >>

Written by:

343 Posts

View All Posts
Follow Me :