Cloud

GitHub Actions: Deploy Automático en Linux en 20 Minutos

GitHub Actions: Deploy Automático en Linux en 20 Minutos

Hacer deploy manual en 12 servidores cada semana significa copiar, pegar y rezar. Un viernes por la tarde subí un archivo de configuración erróneo a 4 servidores de producción antes de notar el error. El downtime duró 40 minutos. Un approval gate en GitHub Actions habría frenado ese deploy antes de que el archivo alcanzara los servidores. El 45% de los incidentes en producción proviene de errores de configuración manual, según el Puppet State of DevOps Report 2024. Cuando configuré el primer workflow automatizado en 20 minutos para una organización sanitaria pública, eliminé los deploy manuales por completo. GitHub Actions automatiza el deploy en servidores Linux via SSH usando secrets para las credenciales. Configuras un workflow YAML que se activa al push en el branch main, ejecuta comandos remotos y gestiona el rollback si la operación falla. Detectas un fallo antes de que se propague a 2.000 endpoints, en lugar de descubrirlo 40 minutos después.

| Componente YAML | Propósito | Ejemplo |

|—|—|—|

| on.push.branches | Trigger del workflow | [main] |

| runs-on | OS del runner temporal | ubuntu-latest |

| appleboy/ssh-action | Conexión SSH remota | master |

| secrets.SSH_KEY | Clave privada cifrada | ${{ secrets.* }} |

| environment | Approval gate requerido | production |

Requisitos Previos para GitHub Actions / Entorno de Prueba

Antes de empezar, necesitas un servidor Linux accesible via SSH y un repositorio GitHub con el código a deployar. En el servidor, instala git y configura la aplicación (Node.js para esta guía). En tu equipo local, instala git y genera una clave SSH dedicada al deploy. Consulta la documentación oficial de GitHub Actions para revisar los límites de uso y la sintaxis YAML completa.

Configuración del Workflow y Secrets

Un workflow es un archivo YAML dentro de la carpeta .github/workflows/ de tu repositorio. GitHub lo ejecuta automáticamente cuando se verifica el evento especificado en el bloque on. El bloque jobs contiene los pasos ejecutados en un runner temporal, un container Ubuntu que GitHub crea, usa y destruye en minutos. Cada step dentro del job ejecuta una acción o un comando shell.

name: Deploy Production
on:
  push:
    branches: [main]
jobs:
  deploy:
    runs-on: ubuntu-latest
    steps:
      - name: Checkout code
        uses: actions/checkout@v4
      - name: Deploy via SSH
        uses: appleboy/ssh-action@master

El runner descarga el código con actions/checkout, luego usa appleboy/ssh-action para conectarse a tu servidor Linux y ejecutar los comandos de deploy.

Nunca escribas contraseñas o claves privadas en el código. El 73% de los breach parte de credenciales robadas, según el Verizon DBIR 2025. Almacenar secrets en GitHub cumple con el control A.8.2 de ISO 27001:2022 para información criptográfica. Ve a Settings > Secrets and variables > Actions en tu repositorio y crea tres secrets:

  1. SERVER_IP: la dirección IP pública del servidor Linux.
  2. SERVER_USER: el usuario SSH para el deploy, ejemplo deployuser.
  3. SSH_KEY: la clave privada SSH dedicada.

Genera la clave en tu equipo local:

ssh-keygen -t ed25519 -C "github-deploy" -f ~/.ssh/github_deploy_key

Copia el contenido del archivo github_deploy_key.pub en el archivo ~/.ssh/authorized_keys del servidor Linux. Copia el contenido del archivo github_deploy_key (la clave privada) en el secret SSH_KEY en GitHub. Ahora el runner se autentica sin contraseña.

El workflow base conecta el runner al servidor y ejecuta comandos remotos. Usa la acción appleboy/ssh-action, que gestiona la autenticación y la ejecución de forma segura.

# .github/workflows/deploy.yml
on:
  push:
    branches: [main]
jobs:
  deploy:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - name: Deploy via SSH
        uses: appleboy/ssh-action@master
        with:
          host: ${{ secrets.SERVER_IP }}
          username: ${{ secrets.SERVER_USER }}
          key: ${{ secrets.SSH_KEY }}
          script: |
            cd /var/www/myapp
            git pull origin main
            echo "Deploy completado"

Cuando haces push en el branch main, GitHub crea el runner, lee los secrets, se conecta al servidor y ejecuta los comandos definidos en script. Si un comando falla, el workflow se interrumpe y GitHub marca el job como error.

Deploy de una app Node.js

Para una app Node.js, añade los comandos de instalación y reinicio después del git pull. Usa pm2 para gestionar el proceso en background. Todavía no tengo una respuesta definitiva sobre si pm2 será reemplazado por systemd en todos los casos, pero pm2 sigue siendo la opción más rápida para Node.js en deployments ágiles.

      - name: Deploy Node.js via SSH
        uses: appleboy/ssh-action@master
        with:
          host: ${{ secrets.SERVER_IP }}
          username: ${{ secrets.SERVER_USER }}
          key: ${{ secrets.SSH_KEY }}
          script: |
            cd /var/www/nodeapp
            git pull origin main
            npm install --production
            npm run build
            pm2 restart nodeapp --update-env
            pm2 save

El runner ejecuta el pull, instala las dependencias sin los paquetes de development, compila el código y reinicia el proceso con pm2. El flag --update-env refresca las variables de entorno. Las organizaciones que adoptan CI/CD reducen los tiempos de deploy un 60% y los fallos en producción un 30%, según el DORA State of DevOps 2025.

Deploy de un sitio estático

Para sitios estáticos, no necesitas git pull en el servidor. Compila localmente en el runner y transfiere los archivos con scp o rsync.

jobs:
  deploy-static:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - name: Build static site
        run: npm install && npm run build
      - name: Transfer files via SSH
        uses: appleboy/scp-action@master
        with:
          host: ${{ secrets.SERVER_IP }}
          username: ${{ secrets.SERVER_USER }}
          key: ${{ secrets.SSH_KEY }}
          source: "dist/*"
          target: "/var/www/html"

La acción appleboy/scp-action copia los archivos compilados en la carpeta dist directamente en la webroot del servidor, reemplazando los archivos viejos.

Rollback, Notificaciones y Approval Gates

Si el deploy falla, debes volver a la versión anterior en pocos segundos. Usa una estrategia basada en symlink. Mantienes dos carpetas: current (symlink a la versión activa) y releases (carpetas numeradas por versión).

      - name: Rollback on failure
        if: failure()
        uses: appleboy/ssh-action@master
        with:
          host: ${{ secrets.SERVER_IP }}
          username: ${{ secrets.SERVER_USER }}
          key: ${{ secrets.SSH_KEY }}
          script: |
            ln -sfn /var/www/releases/previous /var/www/current
            pm2 restart nodeapp
            echo "Rollback ejecutado"

El bloque if: failure() activa este step solo si uno de los pasos anteriores falla. El symlink apunta a la última versión estable. El servicio reinicia con el código funcional.

Recibir un alerta cuando el deploy termina te permite intervenir de inmediato si algo falla. Configura un bot Telegram y usa curl en el workflow.

      - name: Notify Telegram on success
        if: success()
        run: |
          curl -s -X POST https://api.telegram.org/bot${{ secrets.TELEGRAM_TOKEN }}/sendMessage
          -d chat_id=${{ secrets.TELEGRAM_CHAT_ID }}
          -d text="Deploy completado en ${{ secrets.SERVER_IP }}"

Añade TELEGRAM_TOKEN y TELEGRAM_CHAT_ID a los secrets del repositorio. Para el fallo, duplica el step con if: failure() y cambia el texto del alerta.

El bloque on: push: branches: [main] limita el trigger al branch principal. Si quieres control extra, usa las environment protection rules. Crea un environment llamado production en Settings > Environments. Añade un required reviewer, un colega que debe aprobar el deploy antes de que el runner parta.

jobs:
  deploy:
    runs-on: ubuntu-latest
    environment: production
    steps:
      - uses: actions/checkout@v4

Cuando haces push en main, GitHub detiene el workflow y envía una notificación al reviewer. El reviewer clica Approve. Solo entonces el runner se conecta al servidor. Este approval gate habría frenado aquel viernes por la tarde con el archivo de configuración erróneo, impidiendo que el código defectuoso alcanzara los 4 servidores.

Errores Comunes y Resolución de Problemas

Permission denied (publickey): el runner no accede al servidor. Verifica que la clave pública esté en el archivo authorized_keys del usuario remoto y que la clave privada en el secret SSH_KEY incluya todo el contenido del archivo, incluida la línea final.

Host key verification failed: el runner no conoce el fingerprint del servidor. Añade script_stop: true y configura debug: true en appleboy/ssh-action para ver la salida completa de la conexión SSH.

Secrets no encontrados: el workflow devuelve errores sobre las variables. Comprueba que los nombres de los secrets en Settings coincidan exactamente con los del YAML. Un carácter distinto hace el secret invisible para el runner.

Comandos largos que se bloquean: SSH cierra la conexión si un comando no produce output durante mucho tiempo. Añade opciones de timeout o divide los comandos en steps separados.

Conclusiones con Puntos Clave Operativos

  1. Sustituye el deploy manual con GitHub Actions. El workflow YAML elimina el copiar y pegar y reduce los errores humanos un 45%.
  2. Usa secrets para las credenciales SSH. Sin claves privadas en el repositorio, sin contraseñas en claro en los logs.
  3. Añade rollback automático y approval gate. Si el deploy falla, vuelves a la versión estable. Si el código es erróneo, un colega lo frena antes de que llegue a producción.

Lee también: Docker para Sysadmin: Primer Contenedor en Producción

Comparte este artículo:

Escrito por

Rosario Giordano

Rosario Giordano es administrador de sistemas y consultor de TI especializado en c iberseguridad y cloud, con más de 20 años de experiencia en la gestión de infraestructuras Linux empresariales. Sus áreas de especialización incluyen el hardening de SSH, plataformas Kubernetes , bases de datos PostgreSQL, virtualización con VMware/Proxmox y el cumplimiento de los marcos de seguridad NIS2 e ISO 27001.