← Volver al blog
·8 min de lectura

Deploy de aplicaciones Node.js con PM2 en produccion

DevOpsNode.js

Cuando desarrollas una aplicacion Node.js, ejecutarla con node index.js funciona perfectamente en tu maquina local. Pero en produccion, esa aproximacion tiene problemas serios: si la aplicacion crashea, nadie la reinicia; si el servidor se reinicia, tu app no levanta sola; y si tienes un servidor con multiples nucleos, solo estas usando uno. PM2 resuelve todos estos problemas y mas.

Que es PM2 y por que lo necesitas

PM2 es un gestor de procesos para aplicaciones Node.js disenado especificamente para entornos de produccion. Sus capacidades principales incluyen:

  • Reinicio automatico: si tu aplicacion crashea por un error no capturado, PM2 la reinicia inmediatamente.
  • Modo cluster: permite ejecutar multiples instancias de tu app para aprovechar todos los nucleos del CPU.
  • Gestion de logs: centraliza y rota los logs de todas tus aplicaciones.
  • Monitoreo: metricas de CPU, memoria y reinicio en tiempo real.
  • Startup scripts: tu aplicacion sobrevive reinicios del servidor.
  • Zero-downtime reload: actualiza tu aplicacion sin perder una sola peticion.

Instalacion y primeros pasos

Instala PM2 de forma global en tu servidor:

npm install -g pm2

La forma mas basica de iniciar una aplicacion es:

pm2 start app.js

Esto inicia el proceso, le asigna un ID y un nombre (por defecto el nombre del archivo). Puedes verificar el estado con:

pm2 status

Veras una tabla con el ID, nombre, modo, PID, estado, uso de CPU y memoria de cada proceso. Otros comandos basicos que usaras constantemente:

pm2 stop app          # Detener por nombre
pm2 restart app       # Reiniciar
pm2 delete app        # Eliminar del registro de PM2
pm2 logs app          # Ver logs en tiempo real
pm2 logs app --lines 200  # Ver ultimas 200 lineas

El archivo ecosystem: configuracion profesional

Ejecutar pm2 start app.js esta bien para pruebas rapidas, pero en produccion necesitas un archivo de configuracion que defina exactamente como debe ejecutarse tu aplicacion. Este archivo es ecosystem.config.js:

module.exports = {
  apps: [
    {
      name: "mi-api",
      script: "./src/index.js",
      instances: "max",
      exec_mode: "cluster",
      env: {
        NODE_ENV: "production",
        PORT: 3000
      },
      env_staging: {
        NODE_ENV: "staging",
        PORT: 3001
      },
      max_memory_restart: "500M",
      log_date_format: "YYYY-MM-DD HH:mm:ss Z",
      error_file: "./logs/error.log",
      out_file: "./logs/output.log",
      merge_logs: true,
      autorestart: true,
      max_restarts: 10,
      min_uptime: "10s",
      restart_delay: 4000,
      watch: false,
      ignore_watch: ["node_modules", "logs"],
    }
  ]
};

Para iniciar con este archivo:

pm2 start ecosystem.config.js

Para usar las variables de un entorno especifico:

pm2 start ecosystem.config.js --env staging

Desglosemos las opciones mas relevantes del ecosystem file.

instances y exec_mode

Estas dos opciones trabajan juntas. Cuando defines exec_mode: "cluster" e instances: "max", PM2 crea una instancia de tu aplicacion por cada nucleo del CPU disponible. En un servidor con 4 nucleos, tendras 4 procesos sirviendo peticiones en paralelo.

Puedes usar un numero fijo (instances: 2) o un porcentaje (instances: "50%") si no quieres saturar el servidor. El modo cluster usa el modulo cluster nativo de Node.js, asi que funciona de forma transparente con servidores HTTP.

max_memory_restart

Esta opcion es critica para prevenir memory leaks. Si un proceso supera el umbral definido (por ejemplo, "500M"), PM2 lo reinicia automaticamente. Esto no es una solucion al leak, pero evita que tu servidor se quede sin memoria mientras investigas el problema.

min_uptime y max_restarts

Estas opciones previenen loops de reinicio infinitos. Si tu aplicacion crashea antes de cumplir min_uptime (por ejemplo, 10 segundos), PM2 la cuenta como un reinicio fallido. Despues de alcanzar max_restarts reinicios fallidos, PM2 detiene la aplicacion en lugar de seguir intentando. Esto te protege de una aplicacion que tiene un error fatal en el arranque y consumiria recursos reiniciandose indefinidamente.

restart_delay

Agrega un delay entre reinicios. Util cuando tu aplicacion depende de un servicio externo que podria estar temporalmente caido. Un delay de 4000ms evita bombardear ese servicio con intentos de conexion rapidos.

Gestion de logs

Los logs son fundamentales para diagnosticar problemas en produccion. PM2 centraliza los logs de todas tus aplicaciones. Los comandos principales son:

pm2 logs                    # Todos los logs en tiempo real
pm2 logs mi-api             # Solo logs de una app
pm2 logs --lines 500        # Ultimas 500 lineas
pm2 flush                   # Limpiar todos los logs

El problema es que los archivos de log crecen indefinidamente. Para esto existe pm2-logrotate:

pm2 install pm2-logrotate

Configuralo segun tus necesidades:

pm2 set pm2-logrotate:max_size 50M       # Rotar cuando llegue a 50MB
pm2 set pm2-logrotate:retain 10           # Mantener 10 archivos rotados
pm2 set pm2-logrotate:compress true       # Comprimir logs rotados
pm2 set pm2-logrotate:dateFormat YYYY-MM-DD_HH-mm-ss
pm2 set pm2-logrotate:rotateInterval '0 0 * * *'  # Rotar diariamente

Sobrevivir reinicios del servidor

De nada sirve tener PM2 si al reiniciar el servidor tus aplicaciones no levantan. Para esto, PM2 genera un script de startup para tu sistema operativo:

pm2 startup

Este comando imprimira una linea que debes ejecutar con permisos de root. Por ejemplo, en un servidor Ubuntu:

sudo env PATH=$PATH:/usr/bin pm2 startup systemd -u deploy --hp /home/deploy

Despues de configurar todas tus aplicaciones, guarda el estado actual:

pm2 save

Ahora, cuando el servidor reinicie, PM2 arrancara automaticamente y restaurara todas las aplicaciones que tenia corriendo.

Zero-downtime reloads

Esta es una de las funcionalidades mas valiosas de PM2. Cuando necesitas actualizar tu aplicacion, un pm2 restart mata todos los procesos y los vuelve a crear, lo que genera unos segundos de downtime. En cambio, pm2 reload actualiza los procesos uno por uno en modo cluster:

pm2 reload mi-api

PM2 inicia una nueva instancia, espera a que este lista, redirige el trafico a ella y recien entonces mata la instancia anterior. Repite este proceso con cada instancia del cluster. El resultado es que siempre hay al menos una instancia atendiendo peticiones.

Para que esto funcione correctamente, tu aplicacion debe manejar la senal SIGINT para cerrar conexiones de forma ordenada:

process.on('SIGINT', () => {
  // Cerrar conexiones a base de datos
  // Terminar requests en progreso
  server.close(() => {
    process.exit(0);
  });
});

Tambien puedes configurar el tiempo maximo de espera antes de forzar el kill:

// En ecosystem.config.js
kill_timeout: 5000,  // 5 segundos para cerrar limpiamente
listen_timeout: 8000 // 8 segundos para considerar la app lista

Workflow de deploy tipico

Un flujo de despliegue comun con PM2 se ve asi:

#!/bin/bash
# deploy.sh
cd /var/www/mi-api

# Traer ultimos cambios
git pull origin main

# Instalar dependencias (solo produccion)
npm ci --production

# Ejecutar migraciones si las hay
npm run migrate

# Reload sin downtime
pm2 reload ecosystem.config.js --env production

# Verificar que todo esta corriendo
pm2 status

Si usas CI/CD (GitHub Actions, GitLab CI, etc.), este script se ejecuta automaticamente despues de cada push a la rama principal.

Monitoreo

PM2 incluye un monitor basico en la terminal:

pm2 monit

Esto muestra un dashboard interactivo con metricas de CPU, memoria, loop delay y logs de cada proceso. Para monitoreo mas avanzado, PM2 ofrece PM2 Plus (antes Keymetrics), una plataforma web que incluye alertas, metricas historicas y debugging remoto.

Tambien puedes consultar metricas por API para integrarlo con tus propias herramientas de monitoreo:

pm2 prettylist          # Informacion detallada en formato JSON legible
pm2 show mi-api         # Detalle de un proceso especifico

Health checks y buenas practicas

Algunas recomendaciones finales para un setup robusto en produccion:

  1. No ejecutes PM2 como root. Crea un usuario dedicado para tu aplicacion.
  2. Configura max_memory_restart siempre. Un memory leak sin este limite puede tumbar tu servidor entero.
  3. Usa cluster mode si tu aplicacion es stateless. Si maneja estado en memoria (sesiones, websockets), necesitas sticky sessions o un store externo como Redis.
  4. Monitorea los reinicios. Si una app se reinicia frecuentemente, hay un problema que debes investigar, no solo dejar que PM2 lo "solucione".
  5. Implementa un health check endpoint (/health) que verifique conexiones a base de datos y servicios externos. Esto facilita la integracion con balanceadores de carga.
app.get('/health', async (req, res) => {
  try {
    await db.query('SELECT 1');
    res.status(200).json({ status: 'ok', uptime: process.uptime() });
  } catch (error) {
    res.status(503).json({ status: 'error', message: 'Database unavailable' });
  }
});
  1. Mantene el ecosystem file en tu repositorio. Esto asegura que la configuracion de produccion esta versionada y es reproducible.

PM2 es una herramienta madura y estable que resuelve la mayoria de problemas operacionales de Node.js en produccion. Configurado correctamente, puede mantener tus aplicaciones corriendo de forma confiable durante meses sin intervencion manual.