terminal

Portafolio

arrow_backVolver a las guías
calendar_today10 de Junio, 2026schedule10 min de lecturaAvanzado

Despliegue Completo en VPS

Guía paso a paso para desplegar una aplicación Next.js moderna con PM2, Redis, Nginx como proxy inverso, certificado SSL con Certbot y CI/CD usando GitHub Actions.

#Next.js#Deploy#Redis

🚀 Guía Completa de Despliegue en VPS (Next.js + PM2 + Redis + Nginx + SSL + GitHub Actions)

Esta guía documenta paso a paso el proceso de despliegue en producción de tu aplicación, las decisiones de arquitectura tomadas, comandos útiles, precauciones y solución de errores comunes.


🏗️ Arquitectura del Despliegue

La aplicación se compone de:

  1. Frontend y API Route Handlers (Next.js) corriendo localmente en el puerto <PUERTO_APP> (ej. 3000).
  2. Worker en segundo plano (BullMQ + tsx) encargado de procesar tareas asíncronas y de IA.
  3. Servidor Redis (Docker) escuchando en el puerto local <PUERTO_REDIS> (ej. 6379).
  4. Nginx actuando como proxy inverso, redirigiendo el tráfico seguro del puerto 80/443 a la aplicación en el puerto <PUERTO_APP>.
  5. Certbot (Let's Encrypt) para gestionar el certificado SSL HTTPS de forma gratuita y automática.
  6. CI/CD (GitHub Actions) para automatizar la integración y despliegue continuo cada vez que se hace push a la rama principal (ej. main).

📋 Paso a Paso del Despliegue

Paso 1: Configurar DNS

Antes de configurar el servidor, debes indicarle al dominio a dónde apuntar.

  1. Entra a tu proveedor de dominio (ej. Cloudflare) y ve a la sección DNS.
  2. Añade un nuevo registro tipo A:
    • Type: A
    • Name: <subdominio> (ej. meetings para crear el subdominio meetings.tu-dominio.com u @ para el dominio raíz).
    • IPv4 Address: La IP pública de tu VPS (ej. <IP_PUBLICA_VPS>).
    • Proxy Status: Activo (o solo DNS, según tus necesidades).

Paso 2: Preparación del VPS y Permisos del Usuario

Es una mala práctica correr tus aplicaciones Node.js o el pipeline de despliegue continuo como usuario root. Por ello se utiliza un usuario con privilegios limitados (ej. <usuario_deploy>).

  1. Crear la carpeta del proyecto en el servidor:
    mkdir -p /var/www/<nombre-proyecto>
    
  2. Clonar el repositorio dentro de la carpeta:
    cd /var/www/
    git clone https://github.com/<tu-usuario>/<tu-repositorio>.git
    
  3. Mover la propiedad al usuario <usuario_deploy>: Si clonaste como root, debes transferir la propiedad al usuario que usará GitHub Actions:
    chown -R <usuario_deploy>:<usuario_deploy> /var/www/<nombre-proyecto>
    

Paso 3: Configurar las Variables de Entorno (.env)

Crea el archivo .env en la raíz de tu proyecto /var/www/<nombre-proyecto>/ con tus credenciales de producción:

# Configuración del servidor y base de datos
PORT=<PUERTO_APP>
NEXT_PUBLIC_SUPABASE_URL=https://<tu-proyecto>.supabase.co
SUPABASE_SERVICE_ROLE_KEY=tu_service_role_key
DATABASE_URL="postgresql://<usuario_db>:<password_db>@<host_db>:<puerto_db>/<nombre_db>"

# Configuración de Redis
REDIS_HOST=127.0.0.1
REDIS_PORT=<PUERTO_REDIS>
REDIS_PASSWORD=
REDIS_DB=1 # Se recomienda usar una base de datos específica para no colisionar con otros proyectos

💡 Nota: Al dejar REDIS_PASSWORD vacío, se asume que tu Redis local no tiene contraseña. Al especificar una DB diferente, aislamos los datos de este proyecto de otros que usen el mismo contenedor.


Paso 4: Proxy Inverso con Nginx

Nginx recibe las conexiones en los puertos web estándar y las delega internamente al puerto <PUERTO_APP>.

  1. Crear archivo de configuración:
    sudo nano /etc/nginx/sites-available/<dominio_o_subdominio>
    
  2. Pegar configuración básica de proxy inverso:
    server {
        listen 80;
        server_name <dominio_o_subdominio>;
    
        location / {
            proxy_pass http://127.0.0.1:<PUERTO_APP>;
            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;
            proxy_set_header X-Forwarded-Proto $scheme;
        }
    }
    
  3. Habilitar el sitio y reiniciar Nginx:
    sudo ln -s /etc/nginx/sites-available/<dominio_o_subdominio> /etc/nginx/sites-enabled/
    sudo nginx -t  # Verificar que la sintaxis esté correcta
    sudo systemctl restart nginx
    

Paso 5: Habilitar HTTPS con Certbot (SSL)

Consigue que la conexión sea cifrada con un certificado SSL válido.

sudo certbot --nginx -d <dominio_o_subdominio>

Sigue las instrucciones en pantalla, selecciona la opción para redirigir todo el tráfico HTTP a HTTPS de manera automática.


Paso 6: Configurar e iniciar PM2

PM2 mantiene los procesos vivos en segundo plano.

  1. Iniciar la aplicación Next.js:
    pm2 start npm --name "<nombre-app>" -- run start -- -p <PUERTO_APP>
    
  2. Iniciar el Worker utilizando el ejecutable o script correspondiente:
    pm2 start pnpm --name "<nombre-worker>" -- exec tsx <ruta_al_worker>.ts
    
  3. Guardar el listado actual en PM2: (Esto asegura que si el VPS se reinicia, ambos procesos se levanten solos).
    pm2 save
    

🛠️ GitHub Actions (CI/CD Automático)

El archivo de configuración de GitHub Actions en tu repositorio se ubica en .github/workflows/<archivo_despliegue>.yml.

Secretos requeridos en el repositorio de GitHub:

Ve a Settings -> Secrets and variables -> Actions -> New repository secret:

  • SSH_PRIVATE_KEY: Tu clave SSH privada.
  • SERVER_IP: La IP de tu servidor (<IP_PUBLICA_VPS>).
  • SERVER_USER: El usuario del servidor configurado (<usuario_deploy>).

⚠️ Precauciones y Errores Comunes

1. El Puerto ya está en uso (EADDRINUSE)

  • Causa: Hay un proceso de Node.js huérfano (zombie) corriendo fuera de PM2 que está ocupando el puerto.
  • Solución: Fuerza la terminación del proceso con:
    kill -9 $(lsof -t -i:<PUERTO_APP>) 2>/dev/null || fuser -k <PUERTO_APP>/tcp 2>/dev/null || true
    

2. Conflicto de Permisos (EACCES: permission denied, unlink ...)

  • Causa: Se ejecutaron comandos como root dentro de la carpeta del proyecto, haciendo que el usuario <usuario_deploy> pierda la facultad de escribir o borrar archivos.
  • Solución: Ejecuta esto en tu VPS como root para limpiar y devolver la propiedad a <usuario_deploy>:
    rm -rf /var/www/<nombre-proyecto>/node_modules
    chown -R <usuario_deploy>:<usuario_deploy> /var/www/<nombre-proyecto>
    
    Regla de Oro: Nunca ejecutes comandos de instalación como root en la carpeta del proyecto.

3. El comando tsx no es encontrado (Command "tsx" not found)

  • Causa:
    • tsx no está instalado globalmente en el VPS.
    • O bien la instalación se ejecutó en modo producción omitiendo devDependencies donde reside tsx.
  • Solución:
    1. Asegurar la instalación de dependencias requeridas en el despliegue con:
      pnpm install --frozen-lockfile --prod=false
      
    2. Arrancar el worker usando pnpm exec tsx o la ruta local al ejecutable:
      pm2 start pnpm --name "<nombre-worker>" -- exec tsx <ruta_al_worker>.ts
      

4. Lockfile desactualizado (ERR_PNPM_OUTDATED_LOCKFILE)

  • Causa: Se modificó manualmente el archivo package.json en local pero no se actualizó el lockfile correspondiente antes de hacer el push.
  • Solución: Corre la instalación de dependencias en local, haz commit del archivo lockfile generado y súbelo.

🔍 Comandos de Diagnóstico Útiles

  • Ver estado general de tus apps: pm2 status
  • Ver logs en vivo de una app específica: pm2 logs <nombre-app> o pm2 logs <nombre-worker>
  • Reiniciar una app: pm2 restart <nombre-app>
  • Monitorear recursos (CPU/RAM): pm2 monit
  • Ver qué puertos están escuchando en el VPS: ss -tulnp | grep LISTEN
  • Verificar que Redis esté corriendo: docker ps | grep redis
  • Acceder a la consola de Redis en Docker o CLI: docker exec -it <nombre_contenedor_redis> redis-cli o redis-cli