Despliegue de Aplicaciones Node.js con PM2: Guía Completa de Producción

Introducción

PM2 (Process Manager 2) es un gestor de procesos de grado de producción para aplicaciones Node.js que proporciona reinicios automáticos, balanceo de carga, monitoreo y capacidades de despliegue. Es el estándar de la industria para mantener aplicaciones Node.js vivas para siempre, gestionar registros de aplicaciones y asegurar despliegues sin tiempo de inactividad. Esta guía completa cubre todo desde la instalación básica de PM2 hasta despliegues avanzados en producción, monitoreo y solución de problemas.

Lo Que Aprenderás

  • Instalación y configuración completa de PM2
  • Despliegue y gestión de procesos de aplicaciones
  • Modo cluster y balanceo de carga
  • Despliegues y actualizaciones sin tiempo de inactividad
  • Gestión de registros y monitoreo
  • Scripts de inicio y recuperación automática
  • Configuración de archivos ecosystem de PM2
  • Optimización de rendimiento
  • Integración con herramientas de monitoreo
  • Solución de problemas comunes

¿Por Qué PM2?

  • Gestión de Procesos: Mantiene aplicaciones ejecutándose para siempre
  • Balanceo de Carga: Modo cluster integrado para CPUs multi-núcleo
  • Sin Tiempo de Inactividad: Recarga aplicaciones sin interrupciones
  • Monitoreo: Monitoreo de aplicaciones en tiempo real
  • Gestión de Registros: Recopilación y rotación centralizadas de registros
  • Scripts de Inicio: Inicio automático al reiniciar el servidor
  • Despliegue Fácil: Flujo de trabajo de despliegue simple
  • Gestión de Recursos: Optimización de uso de memoria y CPU

Requisitos Previos

  • Node.js y npm instalados (ver guía de instalación de Node.js)
  • Ubuntu 20.04+, Debian 10+, CentOS 8+ o Rocky Linux 8+
  • Acceso root o sudo
  • Aplicación básica de Node.js lista para despliegue
  • Al menos 512MB de RAM (se recomienda 1GB+)

Instalación

Instalar PM2 Globalmente

# Instalar PM2
npm install pm2 -g

# Verificar instalación
pm2 --version

# Verificar que PM2 esté en PATH
which pm2

# Si hay errores de permisos, ver sección de solución de problemas

Actualizar PM2

# Actualizar PM2 a la última versión
npm install pm2@latest -g

# Actualizar daemon de PM2
pm2 update

Configuración

Gestión Básica de Aplicaciones

Iniciar Aplicaciones

# Iniciar aplicación simple de Node.js
pm2 start app.js

# Iniciar con nombre personalizado
pm2 start app.js --name "my-api"

# Iniciar con modo watch (reinicio automático al cambiar archivos)
pm2 start app.js --watch

# Iniciar con argumentos específicos de Node.js
pm2 start app.js --node-args="--max-old-space-size=4096"

# Iniciar script npm
pm2 start npm --name "my-app" -- start

# Iniciar con variables de entorno
pm2 start app.js --env production

Gestionar Aplicaciones

# Listar todas las aplicaciones
pm2 list
pm2 ls

# Mostrar información detallada
pm2 show my-api

# Detener aplicación
pm2 stop my-api
pm2 stop 0

# Reiniciar aplicación
pm2 restart my-api

# Recargar aplicación (sin tiempo de inactividad)
pm2 reload my-api

# Eliminar aplicación de PM2
pm2 delete my-api

# Detener todas las aplicaciones
pm2 stop all

# Reiniciar todas las aplicaciones
pm2 restart all

# Eliminar todas las aplicaciones
pm2 delete all

Modo Cluster para Balanceo de Carga

El modo cluster ejecuta múltiples instancias de tu aplicación para utilizar todos los núcleos de CPU:

# Iniciar en modo cluster con CPUs máximas
pm2 start app.js -i max

# Iniciar con número específico de instancias
pm2 start app.js -i 4

# Iniciar y auto-escalar según uso de CPU
pm2 start app.js -i max --max-memory-restart 300M

# Escalar hacia arriba o abajo
pm2 scale my-api +2  # Agregar 2 instancias más
pm2 scale my-api 4   # Establecer exactamente 4 instancias

Archivo Ecosystem de PM2

Crea un archivo ecosystem.config.js para configuraciones complejas:

# Generar archivo ecosystem
pm2 ecosystem

# O crear manualmente
cat > ecosystem.config.js <<'EOF'
module.exports = {
  apps: [
    {
      name: 'api-server',
      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: '1G',
      node_args: '--max-old-space-size=4096',
      watch: false,
      ignore_watch: ['node_modules', 'logs'],
      max_restarts: 10,
      min_uptime: '10s'
    },
    {
      name: 'worker',
      script: './worker.js',
      instances: 2,
      exec_mode: 'cluster',
      cron_restart: '0 0 * * *',  // Reiniciar diariamente a medianoche
      env: {
        NODE_ENV: 'development',
        WORKER_TYPE: 'background'
      },
      env_production: {
        NODE_ENV: 'production',
        WORKER_TYPE: 'background'
      }
    }
  ]
};
EOF

# Iniciar usando archivo ecosystem
pm2 start ecosystem.config.js

# Iniciar con entorno específico
pm2 start ecosystem.config.js --env production

# Actualizar aplicaciones desde archivo ecosystem
pm2 reload ecosystem.config.js

Opciones de Configuración Avanzadas

ecosystem.config.js completo con todas las opciones:

module.exports = {
  apps: [{
    // Configuración básica
    name: 'my-app',
    script: './app.js',
    cwd: '/var/www/my-app',

    // Configuración de instancias
    instances: 'max',
    exec_mode: 'cluster',

    // Variables de entorno
    env: {
      NODE_ENV: 'development',
      PORT: 3000,
      DATABASE_URL: 'mongodb://localhost:27017/dev'
    },
    env_production: {
      NODE_ENV: 'production',
      PORT: 8080,
      DATABASE_URL: 'mongodb://production-db:27017/prod'
    },

    // Registros
    error_file: './logs/err.log',
    out_file: './logs/out.log',
    log_file: './logs/combined.log',
    log_date_format: 'YYYY-MM-DD HH:mm:ss Z',
    merge_logs: true,

    // Límites de recursos
    max_memory_restart: '1G',
    max_restarts: 10,
    min_uptime: '10s',
    listen_timeout: 3000,
    kill_timeout: 5000,

    // Configuración de Node.js
    node_args: '--max-old-space-size=2048',

    // Configuración de watch
    watch: false,
    ignore_watch: ['node_modules', 'logs', '*.log'],
    watch_options: {
      followSymlinks: false
    },

    // Configuración de reinicio
    autorestart: true,
    cron_restart: '0 0 * * *',
    restart_delay: 4000,

    // Características avanzadas
    source_map_support: true,
    instance_var: 'INSTANCE_ID',

    // Comandos post-despliegue
    post_update: ['npm install', 'echo Despliegue finalizado']
  }]
};

Despliegue

Flujo de Trabajo de Despliegue Simple

# Despliegue inicial
pm2 start app.js --name "production-api" -i max
pm2 save
pm2 startup

# Actualizar despliegue
git pull origin main
npm install --production
pm2 reload production-api

# O con archivo ecosystem
pm2 start ecosystem.config.js --env production
pm2 save

Despliegue Sin Tiempo de Inactividad

# Crear script de despliegue
cat > deploy.sh <<'EOF'
#!/bin/bash

echo "Iniciando despliegue..."

# Obtener último código
git pull origin main

# Instalar dependencias
npm install --production

# Ejecutar migraciones de base de datos (si es necesario)
npm run migrate

# Recargar aplicación con cero tiempo de inactividad
pm2 reload ecosystem.config.js --env production

# Verificar estado
pm2 status

echo "¡Despliegue completado exitosamente!"
EOF

chmod +x deploy.sh

# Ejecutar despliegue
./deploy.sh

Configuración de Script de Inicio

Configura PM2 para iniciar aplicaciones al reiniciar el servidor:

# Generar script de inicio
pm2 startup

# Esto mostrará un comando como:
# sudo env PATH=$PATH:/usr/bin pm2 startup systemd -u username --hp /home/username

# Copiar y ejecutar el comando generado

# Guardar lista actual de procesos de PM2
pm2 save

# Probar reiniciando
sudo reboot

# Después del reinicio, verificar que PM2 esté en ejecución
pm2 list

Despliegue Blue-Green

Crea una estrategia de despliegue blue-green:

cat > ecosystem-blue.config.js <<EOF
module.exports = {
  apps: [{
    name: 'app-blue',
    script: './app.js',
    instances: 'max',
    exec_mode: 'cluster',
    env: {
      PORT: 3000,
      NODE_ENV: 'production'
    }
  }]
};
EOF

cat > ecosystem-green.config.js <<EOF
module.exports = {
  apps: [{
    name: 'app-green',
    script: './app.js',
    instances: 'max',
    exec_mode: 'cluster',
    env: {
      PORT: 3001,
      NODE_ENV: 'production'
    }
  }]
};
EOF

# Desplegar green mientras blue está en ejecución
pm2 start ecosystem-green.config.js

# Cambiar Nginx/Load balancer a green
# Una vez estable, detener blue
pm2 stop app-blue

Aplicación Express de Ejemplo

Crea una aplicación Express de ejemplo para pruebas:

# Crear directorio de aplicación
mkdir -p /var/www/my-api
cd /var/www/my-api

# Inicializar proyecto npm
npm init -y

# Instalar dependencias
npm install express

# Crear app.js
cat > app.js <<'EOF'
const express = require('express');
const app = express();
const port = process.env.PORT || 3000;

// Endpoint de verificación de salud
app.get('/health', (req, res) => {
  res.json({
    status: 'healthy',
    uptime: process.uptime(),
    timestamp: new Date().toISOString(),
    pid: process.pid
  });
});

// Endpoint principal
app.get('/', (req, res) => {
  res.json({
    message: 'API en ejecución',
    version: '1.0.0',
    instance: process.env.INSTANCE_ID || 0
  });
});

// Manejo de errores
app.use((err, req, res, next) => {
  console.error(err.stack);
  res.status(500).json({ error: '¡Algo salió mal!' });
});

app.listen(port, () => {
  console.log(`Servidor ejecutándose en puerto ${port}`);
});

// Apagado graceful
process.on('SIGINT', () => {
  console.log('Apagando gracefully...');
  process.exit(0);
});
EOF

# Iniciar con PM2
pm2 start app.js -i max --name "my-api"
pm2 save

Monitoreo

Monitoreo en Tiempo Real

# Panel de monitoreo en tiempo real
pm2 monit

# Lista simple con métricas
pm2 list

# Información detallada sobre aplicación específica
pm2 show my-api

# Ver registros en tiempo real
pm2 logs

# Ver registros de aplicación específica
pm2 logs my-api

# Ver solo registros de errores
pm2 logs --err

# Ver últimas 100 líneas
pm2 logs --lines 100

# Registros en formato JSON
pm2 logs --json

Gestión de Registros

# Vaciar todos los registros
pm2 flush

# Recargar todos los registros
pm2 reloadLogs

# Instalar módulo de rotación de registros de PM2
pm2 install pm2-logrotate

# Configurar rotación de registros
pm2 set pm2-logrotate:max_size 10M
pm2 set pm2-logrotate:retain 7
pm2 set pm2-logrotate:compress true
pm2 set pm2-logrotate:dateFormat YYYY-MM-DD_HH-mm-ss
pm2 set pm2-logrotate:workerInterval 30
pm2 set pm2-logrotate:rotateInterval '0 0 * * *'

Monitoreo de Rendimiento

# Uso de CPU y memoria
pm2 list

# Mostrar métricas detalladas
pm2 describe my-api

# Monitorear métricas específicas
pm2 monitor my-api

# Verificar fugas de memoria
pm2 start app.js --max-memory-restart 500M

# Perfilar aplicación
pm2 profile my-api
# Esperar para perfilado
pm2 profile:stop

Integración con PM2 Plus (Keymetrics)

Para monitoreo avanzado en producción:

# Instalar PM2 Plus
pm2 install pm2-server-monit

# Vincular a cuenta de PM2 Plus (opcional)
pm2 link [secret_key] [public_key] [machine_name]

# Monitorear métricas personalizadas en código
const pmx = require('@pm2/io');

const metric = pmx.metric({
  name: 'Usuarios en tiempo real',
  value: () => {
    return userCount;
  }
});

// Acciones personalizadas
pmx.action('Limpiar caché', (reply) => {
  clearCache();
  reply({ success: true });
});

Solución de Problemas

Aplicación No Inicia

# Verificar registros de PM2
pm2 logs my-api --err

# Verificar aplicación con registros detallados
pm2 start app.js --log-date-format="YYYY-MM-DD HH:mm:ss"

# Verificar si el puerto ya está en uso
sudo lsof -i :3000

# Matar proceso en puerto
sudo kill -9 $(sudo lsof -t -i:3000)

# Iniciar con modo watch para depuración
pm2 start app.js --watch --ignore-watch="node_modules"

Fugas de Memoria

# Establecer umbral de reinicio de memoria
pm2 start app.js --max-memory-restart 500M

# Monitorear uso de memoria
pm2 monit

# Perfilar uso de memoria
pm2 profile my-api

# Habilitar snapshot de heap
pm2 start app.js --node-args="--expose-gc"

# Tomar snapshot de heap
kill -USR2 $(pm2 pid my-api)

Alto Uso de CPU

# Verificar uso de CPU
pm2 list

# Reducir número de instancias
pm2 scale my-api 2

# Perfilar uso de CPU
pm2 profile:cpu my-api
# Esperar...
pm2 profile:cpu:stop

# Verificar bucles infinitos en código
pm2 logs my-api --lines 1000

Caídas de Aplicación

# Ver registros de caídas
pm2 logs my-api --err --lines 100

# Aumentar intentos de reinicio
pm2 start app.js --max-restarts 10 --min-uptime 10000

# Agregar manejo de errores
pm2 start app.js --error /var/log/app-error.log

# Verificar excepciones no capturadas
process.on('uncaughtException', (err) => {
  console.error('Excepción no capturada:', err);
  process.exit(1);
});

process.on('unhandledRejection', (reason, promise) => {
  console.error('Rechazo no manejado:', reason);
});

PM2 No Inicia al Reiniciar

# Eliminar script de inicio antiguo
pm2 unstartup

# Regenerar script de inicio
pm2 startup

# Copiar y ejecutar el comando generado

# Guardar lista de procesos de PM2
pm2 save

# Verificar procesos guardados
cat ~/.pm2/dump.pm2

# Probar reinicio
sudo reboot

Puerto Ya en Uso

# Encontrar proceso usando puerto
sudo lsof -i :3000

# Matar proceso
kill -9 $(lsof -t -i:3000)

# O cambiar puerto en configuración de ecosystem
env: {
  PORT: 3001
}

# Reiniciar aplicación
pm2 reload ecosystem.config.js

Mejores Prácticas de Seguridad

Ejecutar como Usuario No-Root

# Crear usuario dedicado
sudo useradd -r -s /bin/bash pm2user

# Establecer propiedad
sudo chown -R pm2user:pm2user /var/www/my-app

# Cambiar a usuario
sudo -u pm2user pm2 start app.js

# Configurar script de inicio para ese usuario
sudo su - pm2user
pm2 startup
# Ejecutar comando generado como root

pm2 save

Variables de Entorno

# Nunca codificar secretos en duro
# Usar archivo ecosystem con variables de entorno

env_production: {
  NODE_ENV: 'production',
  DATABASE_URL: process.env.DATABASE_URL,
  API_KEY: process.env.API_KEY
}

# O usar archivo .env (instalar dotenv)
npm install dotenv

# En app.js
require('dotenv').config();

# Iniciar con archivo env
pm2 start app.js --env production

Seguridad de Registros

# Restringir permisos de archivos de registro
chmod 640 /var/log/pm2/*.log

# Usar rotación de registros
pm2 install pm2-logrotate
pm2 set pm2-logrotate:retain 7

# No registrar datos sensibles
// En código
console.log('Usuario autenticado'); // Bueno
console.log('Contraseña:', password); // ¡Malo!

Actualizar Regularmente

# Actualizar PM2
npm update pm2 -g

# Actualizar dependencias de aplicación
npm update
npm audit fix

# Recargar aplicación
pm2 reload all

Optimización de Rendimiento

Optimización de Modo Cluster

# Instancias óptimas = núcleos de CPU
pm2 start app.js -i max

# O número específico
pm2 start app.js -i 4

# Monitorear y ajustar
pm2 monit
# Si bajo uso de CPU en instancias, reducir número
pm2 scale my-api 2

Optimización de Memoria

# Establecer límites de memoria
pm2 start app.js --max-memory-restart 500M

# Aumentar tamaño de heap de Node.js
pm2 start app.js --node-args="--max-old-space-size=4096"

# Habilitar registros de recolección de basura
pm2 start app.js --node-args="--trace-gc"

Balanceo de Carga con Nginx

upstream nodejs_backend {
    least_conn;
    server 127.0.0.1:3000;
    server 127.0.0.1:3001;
    server 127.0.0.1:3002;
    server 127.0.0.1:3003;
}

server {
    listen 80;
    server_name api.example.com;

    location / {
        proxy_pass http://nodejs_backend;
        proxy_http_version 1.1;
        proxy_set_header Upgrade $http_upgrade;
        proxy_set_header Connection 'upgrade';
        proxy_set_header Host $host;
        proxy_cache_bypass $http_upgrade;
        proxy_set_header X-Real-IP $remote_addr;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
    }
}

Estrategia de Caché

// Implementar caché en aplicación
const NodeCache = require('node-cache');
const cache = new NodeCache({ stdTTL: 600 });

app.get('/api/data', async (req, res) => {
  const cached = cache.get('data');
  if (cached) {
    return res.json(cached);
  }

  const data = await fetchData();
  cache.set('data', data);
  res.json(data);
});

Conclusión

Has configurado exitosamente PM2 para despliegue de aplicaciones Node.js en producción con estrategias integrales de gestión de procesos, monitoreo y optimización. PM2 asegura que tus aplicaciones se mantengan en línea, funcionen eficientemente y puedan gestionarse con facilidad.

Puntos Clave

  • PM2 proporciona gestión de procesos de grado de producción
  • El modo cluster habilita balanceo de carga entre núcleos de CPU
  • Las recargas sin tiempo de inactividad mantienen las aplicaciones disponibles durante actualizaciones
  • Los archivos ecosystem permiten despliegues consistentes y reproducibles
  • El inicio automático asegura que las aplicaciones sobrevivan reinicios
  • El monitoreo y registro integrados simplifican las operaciones

Mejores Prácticas

  1. Siempre usa ecosystem.config.js para producción
  2. Habilita scripts de inicio para recuperación automática
  3. Implementa rotación de registros para gestionar espacio en disco
  4. Usa modo cluster para utilizar todos los núcleos de CPU
  5. Establece límites de memoria para prevenir caídas del servidor
  6. Monitorea aplicaciones regularmente
  7. Usa recargas sin tiempo de inactividad para actualizaciones
  8. Mantén PM2 y Node.js actualizados

Próximos Pasos

  1. Configurar paneles de monitoreo (PM2 Plus, Grafana)
  2. Implementar despliegues automatizados (CI/CD)
  3. Configurar proxy inverso (Nginx/Apache)
  4. Configurar certificados SSL/TLS
  5. Implementar seguimiento integral de errores
  6. Crear procedimientos de recuperación ante desastres
  7. Configurar entornos de staging y producción

¡PM2 transforma el despliegue de aplicaciones Node.js de complejo a simple, proporcionando las herramientas necesarias para despliegues en producción confiables y escalables!

¡Feliz despliegue!