PM2 Process Management for Node.js in Production

PM2 process management for Node.js production: ecosystem file configuration, zero-downtime restarts, log management, monitoring, clustering, and deployment workflows.

E
ECOSIRE Research and Development Team
|19 de marzo de 20269 min de lectura2.1k Palabras|

Gestión de procesos PM2 para Node.js en producción

Cuando su aplicación Node.js falla a las 2 a.m., PM2 es la diferencia entre que se reinicie automáticamente y que sus usuarios vean una página en blanco hasta que usted se despierte. PM2 es un administrador de procesos probado en batalla que maneja reinicios automáticos, agrupación en clústeres para utilización de múltiples núcleos, agregación de registros e implementaciones sin tiempo de inactividad, todo con un único archivo de configuración que reside en su repositorio.

Esta guía cubre una configuración de producción de PM2 que administra 5 procesos de Node.js simultáneamente: Next.js (frontend), NestJS (API), Docusaurus (docs) y dos sitios de marca. Los patrones se aplican igualmente a implementaciones de proceso único.

Conclusiones clave

  • El archivo ecosystem.config.cjs (CommonJS, no .js) funciona con proyectos ESModule y CommonJS
  • Se requiere el indicador --update-env al reiniciar para seleccionar nuevas variables de entorno
  • Nunca use pm2 restart all sin --update-env después de actualizar .env.local
  • watch: false en producción: la observación de archivos provoca infinitos bucles de reinicio con resultados de compilación
  • max_memory_restart proporciona protección automática contra fugas de memoria sin interrumpir el proceso de forma permanente
  • node_args: '--max-old-space-size=4096' evita fallos de OOM en operaciones con uso intensivo de memoria
  • Los registros de PM2 rotan con el módulo pm2-logrotate; instálelo inmediatamente después del propio PM2
  • pm2 save y pm2 startup conservan su lista de procesos a través de reinicios del servidor

Instalación

# Install PM2 globally
npm install -g pm2

# Install the log rotation module immediately
pm2 install pm2-logrotate

# Configure log rotation
pm2 set pm2-logrotate:max_size 50M
pm2 set pm2-logrotate:retain 7
pm2 set pm2-logrotate:compress true
pm2 set pm2-logrotate:dateFormat YYYY-MM-DD

Archivo de configuración del ecosistema

El archivo ecosystem.config.cjs (formato CommonJS para trabajar con proyectos ESM y CJS) define todos sus procesos:

// ecosystem.config.cjs
module.exports = {
  apps: [
    // ─── Next.js Frontend ────────────────────────────────────────────
    {
      name: 'ecosire-web',
      script: 'node_modules/.bin/next',
      args: 'start',
      cwd: '/opt/ecosire/app/apps/web',
      instances: 1,        // Single instance — Next.js handles its own multi-threading
      exec_mode: 'fork',
      env: {
        NODE_ENV: 'production',
        PORT: 3000,
      },
      // Memory management
      max_memory_restart: '1G',
      node_args: '--max-old-space-size=1024',
      // Logging
      out_file: '/var/log/pm2/ecosire-web.out.log',
      error_file: '/var/log/pm2/ecosire-web.err.log',
      merge_logs: true,
      log_date_format: 'YYYY-MM-DD HH:mm:ss Z',
      // Restart behavior
      watch: false,
      restart_delay: 3000,
      max_restarts: 10,
      min_uptime: '30s',   // Must stay up 30s to count as successful start
      autorestart: true,
      // Graceful shutdown
      kill_timeout: 30000, // 30 seconds to shut down gracefully
      wait_ready: true,    // Wait for process.send('ready')
      listen_timeout: 60000,
    },

    // ─── NestJS API ──────────────────────────────────────────────────
    {
      name: 'ecosire-api',
      script: 'dist/main.js',
      cwd: '/opt/ecosire/app/apps/api',
      instances: 2,         // Cluster mode for multi-core utilization
      exec_mode: 'cluster',
      env: {
        NODE_ENV: 'production',
        PORT: 3001,
      },
      max_memory_restart: '512M',
      node_args: '--max-old-space-size=512',
      out_file: '/var/log/pm2/ecosire-api.out.log',
      error_file: '/var/log/pm2/ecosire-api.err.log',
      merge_logs: true,
      log_date_format: 'YYYY-MM-DD HH:mm:ss Z',
      watch: false,
      restart_delay: 2000,
      max_restarts: 10,
      min_uptime: '20s',
      autorestart: true,
      kill_timeout: 15000,
      // Graceful cluster reload support
      listen_timeout: 30000,
    },

    // ─── Docusaurus Docs ─────────────────────────────────────────────
    {
      name: 'ecosire-docs',
      script: 'node_modules/.bin/docusaurus',
      args: 'serve',
      cwd: '/opt/ecosire/app/apps/docs',
      instances: 1,
      exec_mode: 'fork',
      env: {
        NODE_ENV: 'production',
        PORT: 3002,
      },
      max_memory_restart: '256M',
      out_file: '/var/log/pm2/ecosire-docs.out.log',
      error_file: '/var/log/pm2/ecosire-docs.err.log',
      merge_logs: true,
      log_date_format: 'YYYY-MM-DD HH:mm:ss Z',
      watch: false,
      restart_delay: 3000,
      max_restarts: 5,
      min_uptime: '30s',
      autorestart: true,
      kill_timeout: 10000,
    },

    // ─── Brand Site: Odovation ───────────────────────────────────────
    {
      name: 'odovation-web',
      script: 'node_modules/.bin/next',
      args: 'start',
      cwd: '/opt/ecosire/app/apps/odovation',
      instances: 1,
      exec_mode: 'fork',
      env: {
        NODE_ENV: 'production',
        PORT: 3010,
      },
      max_memory_restart: '512M',
      out_file: '/var/log/pm2/odovation-web.out.log',
      error_file: '/var/log/pm2/odovation-web.err.log',
      merge_logs: true,
      log_date_format: 'YYYY-MM-DD HH:mm:ss Z',
      watch: false,
      restart_delay: 3000,
      max_restarts: 10,
      min_uptime: '30s',
      autorestart: true,
    },

    // ─── Brand Site: MuhammadAmir ────────────────────────────────────
    {
      name: 'muhammadamir-web',
      script: 'node_modules/.bin/next',
      args: 'start',
      cwd: '/opt/ecosire/app/apps/muhammadamir',
      instances: 1,
      exec_mode: 'fork',
      env: {
        NODE_ENV: 'production',
        PORT: 3020,
      },
      max_memory_restart: '512M',
      out_file: '/var/log/pm2/muhammadamir-web.out.log',
      error_file: '/var/log/pm2/muhammadamir-web.err.log',
      merge_logs: true,
      log_date_format: 'YYYY-MM-DD HH:mm:ss Z',
      watch: false,
      restart_delay: 3000,
      max_restarts: 10,
      min_uptime: '30s',
      autorestart: true,
    },
  ],
};

Comandos principales de PM2

# Start all processes from ecosystem file
pm2 start ecosystem.config.cjs

# Restart all (with updated environment variables)
pm2 restart ecosystem.config.cjs --update-env

# Graceful reload (zero-downtime for cluster mode)
pm2 reload ecosystem.config.cjs

# Stop all processes
pm2 stop all

# Delete all processes from PM2 registry
pm2 delete all

# Individual process management
pm2 restart ecosire-api
pm2 stop ecosire-docs
pm2 logs ecosire-web --lines 100

# Real-time monitoring dashboard
pm2 monit

# Status overview
pm2 status
pm2 list

Inicio al reiniciar el servidor

Sin la configuración de inicio, todos los procesos PM2 se pierden al reiniciar el servidor:

# Generate and install the startup script for your init system
pm2 startup
# Copy the output command and run it (it looks like:)
# sudo env PATH=$PATH:/usr/bin pm2 startup systemd -u ubuntu --hp /home/ubuntu

# Save the current process list
pm2 save
# This creates ~/.pm2/dump.pm2 — processes are restored on reboot

# Verify startup works
pm2 resurrect  # Manually restore from dump.pm2

Cada vez que agregue o elimine procesos, ejecute pm2 save nuevamente para actualizar el archivo de volcado.


Implementaciones sin tiempo de inactividad

Para NestJS en modo clúster, PM2 admite recargas reales sin tiempo de inactividad:

# Reload restarts workers one at a time (zero-downtime)
# Old workers handle requests while new ones start
pm2 reload ecosire-api

# vs restart — kills all workers simultaneously (brief downtime)
pm2 restart ecosire-api

Para Next.js (que se ejecuta en modo bifurcación, instancia única), el tiempo de inactividad cero requiere un enfoque diferente. Utilice la configuración wait_ready + listen_timeout con una señal de inicio de su aplicación:

// apps/web — this is handled automatically by Next.js
// But for NestJS, send the ready signal explicitly:

// apps/api/src/main.ts
async function bootstrap() {
  const app = await NestFactory.create(AppModule);
  await app.listen(3001);

  // Signal PM2 that the process is ready
  if (process.send) {
    process.send('ready');
  }
}

bootstrap();

Gestión de registros

Los registros de PM2 pueden llenar su disco si no se administran. Configure la rotación de registros inmediatamente:

# Install log rotation module
pm2 install pm2-logrotate

# Configuration
pm2 set pm2-logrotate:max_size 50M       # Rotate when log reaches 50MB
pm2 set pm2-logrotate:retain 7           # Keep 7 days of logs
pm2 set pm2-logrotate:compress true      # Gzip rotated logs
pm2 set pm2-logrotate:dateFormat YYYY-MM-DD
pm2 set pm2-logrotate:workerInterval 30  # Check rotation interval (seconds)
pm2 set pm2-logrotate:rotateInterval '0 0 * * *'  # Daily at midnight

Comandos de registro útiles:

# View all logs combined
pm2 logs

# View specific process logs
pm2 logs ecosire-api

# View with timestamps
pm2 logs --timestamp

# Flush all log files
pm2 flush

# Tail error logs only
pm2 logs ecosire-api --err --lines 200

Monitoreo y Métricas

PM2 Plus (anteriormente Keymetrics) proporciona monitoreo basado en la nube. Para monitoreo autohospedado:

# Built-in terminal dashboard
pm2 monit

# Get JSON status for scripting/monitoring integration
pm2 jlist    # JSON process list
pm2 prettylist  # Formatted process list

# Integrate with your monitoring stack
pm2 set pm2-server-monit:interval 5  # Metrics collection interval

Para monitorear la producción, exponga las métricas de PM2 a Prometheus:

npm install -g pm2-prometheus-exporter
pm2 set pm2-prometheus-exporter:port 9209

# Scrape in Prometheus config:
# - job_name: pm2
#   static_configs:
#     - targets: ['localhost:9209']

Integración del script de implementación

Una secuencia de implementación típica:

#!/bin/bash
# scripts/deploy-production.sh

set -e

echo "=== Starting deployment ==="

# 1. Pull latest code
git pull origin main

# 2. Install dependencies
pnpm install --frozen-lockfile

# 3. Build all apps (with Turbo remote cache)
TURBO_TOKEN="$TURBO_TOKEN" TURBO_TEAM="$TURBO_TEAM" \
  npx turbo run build

# 4. Run database migrations
pnpm --filter @ecosire/db db:migrate

# 5. Restart PM2 processes
# --update-env picks up changes in .env.local
pm2 restart ecosystem.config.cjs --update-env

# 6. Wait for processes to stabilize
sleep 10

# 7. Health checks
curl -f https://ecosire.com/ -o /dev/null -s || {
  echo "Web health check failed — rolling back"
  git revert HEAD --no-edit
  pm2 restart ecosystem.config.cjs --update-env
  exit 1
}

curl -f https://api.ecosire.com/api/health -o /dev/null -s || {
  echo "API health check failed — rolling back"
  git revert HEAD --no-edit
  pm2 restart ecosystem.config.cjs --update-env
  exit 1
}

# 8. Save process state
pm2 save

echo "=== Deployment complete ==="

Errores y soluciones comunes

Error 1: Olvidar --update-env

Después de actualizar .env.local, ejecutar pm2 restart all sin --update-env hace que los procesos se reinicien con las variables de entorno antiguas. Utilice siempre pm2 restart ecosystem.config.cjs --update-env.

Error 2: usar watch: true en producción

watch: true reinicia el proceso cuando cambia algún archivo. En producción, los resultados de la compilación cambian en cada implementación, lo que provoca infinitos bucles de reinicio. Establezca siempre watch: false.

Error 3: No manejar SIGTERM para un cierre ordenado

PM2 envía SIGTERM al reiniciar/detener. Si su aplicación no lo maneja, PM2 espera kill_timeout milisegundos y envía SIGKILL, lo que puede provocar la pérdida de solicitudes. Manejar SIGTERM en NestJS:

// main.ts
const app = await NestFactory.create(AppModule);
await app.listen(3001);

// Graceful shutdown
process.on('SIGTERM', async () => {
  await app.close();
  process.exit(0);
});

Error 4: quedarse sin espacio en el disco de registro de PM2

Sin pm2-logrotate, los registros de PM2 crecen indefinidamente. Una API con mucho tráfico puede generar gigabytes de registros por día. Instale pm2-logrotate inmediatamente y establezca un max_size (50 MB) y un retain (7 días) razonables.

Error 5: Perder procesos después de reiniciar

pm2 start no persiste los procesos entre reinicios. Ejecute siempre pm2 startup + pm2 save después de la configuración inicial. Si los procesos desaparecen después de reiniciar, ejecute pm2 resurrect para restaurar desde el volcado guardado.


Preguntas frecuentes

¿Cuándo debo utilizar el modo clúster frente al modo bifurcación?

Utilice el modo de clúster para cargas de trabajo vinculadas a la CPU (API de NestJS con computación pesada y procesamiento de datos). El modo de clúster genera instances procesos de trabajo y equilibrios de carga de PM2 entre ellos, aprovechando todos los núcleos de la CPU. Utilice el modo de bifurcación para cargas de trabajo vinculadas a E/S (Next.js, servicio de archivos estáticos) o cuando el proceso no admite la agrupación en clústeres (scripts de un solo subproceso, servicio Docusaurus). Next.js maneja sus propios subprocesos de trabajo internamente, por lo que el modo bifurcación con instances: 1 es correcto.

¿Cómo ejecuto PM2 en un contenedor Docker?

PM2 en Docker usa pm2-docker (o pm2-runtime) en lugar de pm2 para manejar las señales correctamente. La versión en tiempo de ejecución no demoniza (lo que provocaría que Docker se cerrara), reenvía correctamente señales a procesos secundarios y registra en stdout/stderr en lugar de archivos. Utilice CMD ["pm2-runtime", "ecosystem.config.cjs"] en su Dockerfile.

¿Cómo superviso los procesos de PM2 desde una máquina remota?

PM2 Plus (servicio en la nube de pago por proceso) proporciona un panel web. Para el monitoreo autohospedado, exponga las métricas de PM2 a través del exportador Prometheus y visualícelas en Grafana. Para verificaciones de estado simples, puede utilizar SSH y ejecutar pm2 status, o exponer las métricas a través de un punto final HTTP que sondea su sistema de monitoreo.

¿Cuál es la diferencia entre recargar pm2 y reiniciar pm2?

pm2 restart mata a todos los trabajadores simultáneamente y los reinicia; hay un breve período sin trabajadores en ejecución (tiempo de inactividad). pm2 reload es elegante: inicia nuevos trabajadores, espera a que estén listos y luego cierra a los trabajadores antiguos: cero tiempo de inactividad. Utilice pm2 reload para implementaciones de producción. Nota: la recarga solo funciona correctamente en modo clúster; El modo bifurcación retrocede para reiniciar el comportamiento.

¿Cómo configuro diferentes variables de entorno para diferentes procesos?

Cada proceso en ecosystem.config.cjs tiene sus propias secciones env y env_production. La sección env_production se utiliza cuando pasa --env production a comandos PM2. Para los secretos, nunca los coloque directamente en el archivo del ecosistema; configúrelos en el entorno del sistema o en el archivo .env.local y deje que PM2 los herede. El indicador --update-env garantiza que PM2 vuelva a leer las variables de entorno al reiniciar.


Próximos pasos

PM2 es una parte fundamental de cualquier implementación de producción de Node.js. ECOSIRE gestiona cinco procesos PM2 en producción (Next.js, NestJS, Docusaurus y dos sitios de marca) con reinicios automáticos, rotación de registros e implementaciones sin tiempo de inactividad en cada paso a principal.

Ya sea que necesite soporte de ingeniería de DevOps, arquitectura de implementación de producción o ayuda para migrar a una configuración en contenedores, explore nuestros servicios para ver cómo podemos ayudarlo.

E

Escrito por

ECOSIRE Research and Development Team

Construyendo productos digitales de nivel empresarial en ECOSIRE. Compartiendo perspectivas sobre integraciones Odoo, automatización de eCommerce y soluciones empresariales impulsadas por IA.

Chatea en whatsapp