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)