TL;DR — Resumen Rápido

Guía de GitHub Actions: automatice pipelines de despliegue con protección de entornos, secretos, múltiples destinos, caché y estrategias de rollback.

PIPELINE DE DESPLIEGUE AUTOMATIZADO — GITHUB ACTIONS git push disparo Lint + Test calidad Build artefactos Aprobación compuerta Desplegar producción Notificar Slack push → lint → test → build → aprobar → desplegar → notificar

Desplegar manualmente — conectarse por SSH a un servidor, ejecutar comandos de build, cruzar los dedos — es propenso a errores, imposible de auditar y no escala. GitHub Actions permite definir todo el pipeline de despliegue como código: lint, pruebas, build, aprobación, despliegue y notificación — todo activado automáticamente con cada push a main. Esta guía cubre la construcción de un workflow de despliegue automatizado en producción, desde el primer archivo YAML hasta despliegues multi-destino, protección de entornos y estrategias de rollback.

Prerrequisitos

Antes de crear tu workflow de despliegue, ten lo siguiente en su lugar:

  • Un repositorio de GitHub con código de aplicación (ejemplos en Node.js a lo largo de la guía)
  • Un entorno destino — cuenta de Cloudflare Workers, un VPS con acceso SSH, o un runner autohospedado en Windows para IIS
  • Familiaridad básica con YAML — los workflows de GitHub Actions son archivos YAML donde la indentación importa
  • Acceso a la configuración del repositorio en GitHub para guardar secretos y crear entornos

Conceptos Básicos de GitHub Actions

Un workflow de GitHub Actions es un archivo YAML almacenado en .github/workflows/. Se activa mediante uno o más eventos (push, pull request, programación, disparo manual). Un workflow contiene uno o más jobs. Cada job corre en un runner — una máquina virtual hospedada por GitHub (Ubuntu, Windows, macOS) o autohospedada. Cada job contiene steps secuenciales, cada uno de los cuales ejecuta un comando shell (run) o una unidad reutilizable llamada action (uses).

Los bloques clave:

name: Desplegar                   # nombre del workflow en la pestaña Actions

on:                               # disparador
  push:
    branches: [main]

jobs:
  deploy:                         # ID del job
    runs-on: ubuntu-latest        # runner
    steps:
      - uses: actions/checkout@v4 # action (descarga el código fuente)
      - run: npm ci               # comando shell
      - run: npm run build

Los jobs corren en paralelo por defecto. Usa needs: [job-id] para imponer un orden.

Tu Primer Workflow de Despliegue

Aquí hay un workflow completo para un proyecto Node.js que despliega en Cloudflare Workers usando Wrangler. Primero ejecuta lint y pruebas, luego hace build y por último despliega — solo en pushes a main.

name: Desplegar en Cloudflare Workers

on:
  push:
    branches: [main]

concurrency:
  group: deploy-${{ github.ref }}
  cancel-in-progress: true

jobs:
  lint-and-test:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - uses: actions/setup-node@v4
        with:
          node-version: "22"
          cache: "npm"
      - run: npm ci
      - run: npm run lint
      - run: npm test

  build:
    needs: lint-and-test
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - uses: actions/setup-node@v4
        with:
          node-version: "22"
          cache: "npm"
      - run: npm ci
      - run: npm run build
      - uses: actions/upload-artifact@v4
        with:
          name: dist
          path: dist/

  deploy:
    needs: build
    runs-on: ubuntu-latest
    environment: production
    steps:
      - uses: actions/checkout@v4
      - uses: actions/download-artifact@v4
        with:
          name: dist
          path: dist/
      - name: Desplegar con Wrangler
        uses: cloudflare/wrangler-action@v3
        with:
          apiToken: ${{ secrets.CF_API_TOKEN }}
          accountId: ${{ secrets.CLOUDFLARE_ACCOUNT_ID }}
      - name: Notificar éxito en Slack
        if: success()
        run: |
          curl -s -X POST "${{ secrets.SLACK_WEBHOOK }}" \
            -H "Content-Type: application/json" \
            -d "{\"text\":\"Desplegado ${{ github.sha }} en producción por ${{ github.actor }}\"}"

El bloque concurrency en la parte superior evita que dos despliegues se superpongan. Si haces push dos veces seguidas, la primera ejecución se cancela antes de que comience la segunda.

Reglas de Protección de Entorno

Hacer push directamente a producción sin una verificación humana es arriesgado para sistemas críticos. Los entornos de GitHub permiten agregar reglas de protección a cualquier job que los apunte.

Para configurar la protección de entorno:

  1. Ve a Settings → Environments en tu repositorio
  2. Haz clic en New environment y nómbralo production
  3. Activa Required reviewers — agrega uno o más usuarios o equipos de GitHub
  4. Opcionalmente establece un Wait timer (por ejemplo, 10 minutos) antes de que el job pueda ejecutarse
  5. Opcionalmente restringe qué ramas pueden desplegar en este entorno (por ejemplo, solo main)

Una vez configurado, cualquier job con environment: production se pausará y esperará a que un revisor apruebe antes de continuar. El revisor ve el despliegue pendiente en la UI de GitHub y puede aprobar o rechazar con un comentario.

Gestión de Secretos

Nunca escribas tokens de API, claves SSH o contraseñas en los archivos de workflow. GitHub ofrece tres niveles de almacenamiento de secretos:

Secretos de repositorio — Disponibles para todos los workflows del repo. Ve a Settings → Secrets and variables → Actions → New repository secret.

Secretos de entorno — Acotados a un entorno específico (por ejemplo, production). Solo se exponen cuando un job apunta a ese entorno.

Secretos de organización — Compartidos entre múltiples repositorios de una organización de GitHub, ideales para tokens usados por muchos proyectos.

Referencia los secretos en los workflows con la sintaxis ${{ secrets.NOMBRE_SECRETO }}:

- name: Desplegar con Wrangler
  uses: cloudflare/wrangler-action@v3
  with:
    apiToken: ${{ secrets.CF_API_TOKEN }}
    accountId: ${{ secrets.CLOUDFLARE_ACCOUNT_ID }}

OIDC (OpenID Connect) es una alternativa a los secretos de larga duración para proveedores de nube que lo soportan (AWS, GCP, Azure). En lugar de almacenar un token estático, el runner solicita una credencial de corta duración al proveedor en tiempo de ejecución.

Despliegue en Diferentes Destinos

Cloudflare Workers con Wrangler

La action cloudflare/wrangler-action maneja la autenticación y ejecuta cualquier comando de Wrangler:

- uses: cloudflare/wrangler-action@v3
  with:
    apiToken: ${{ secrets.CF_API_TOKEN }}
    accountId: ${{ secrets.CLOUDFLARE_ACCOUNT_ID }}
    command: deploy --env production

VPS mediante SSH y rsync

Para un VPS Linux, usa SSH para conectarte y rsync para transferir archivos:

- name: Desplegar en VPS
  uses: appleboy/ssh-action@v1
  with:
    host: ${{ secrets.DEPLOY_HOST }}
    username: ${{ secrets.DEPLOY_USER }}
    key: ${{ secrets.DEPLOY_SSH_KEY }}
    script: |
      rsync -az --delete dist/ /var/www/myapp/dist/
      systemctl restart myapp

Almacena la clave SSH privada en GitHub Secrets. Agrega la clave pública correspondiente a ~/.ssh/authorized_keys en el servidor. Usa un usuario de despliegue dedicado con permisos mínimos, nunca root.

IIS mediante Runner Autohospedado

Para despliegues en Windows IIS, ejecuta un runner autohospedado directamente en el servidor:

deploy-iis:
  needs: build
  runs-on: [self-hosted, iis-deploy]
  environment: production
  steps:
    - uses: actions/download-artifact@v4
      with:
        name: dist
        path: C:\inetpub\wwwroot\myapp
    - name: Reiniciar sitio IIS
      run: |
        Import-Module WebAdministration
        Restart-WebItem 'IIS:\Sites\MyApp'
      shell: powershell

Caché y Rendimiento

Caché de Dependencias

Re-descargar paquetes npm en cada ejecución desperdicia tiempo. Cachéalos:

- uses: actions/setup-node@v4
  with:
    node-version: "22"
    cache: "npm"

Para .NET:

- uses: actions/cache@v4
  with:
    path: ~/.nuget/packages
    key: ${{ runner.os }}-nuget-${{ hashFiles('**/*.csproj') }}
    restore-keys: ${{ runner.os }}-nuget-

Grupos de Concurrencia

Los grupos de concurrencia evitan despliegues en paralelo — una fuente común de condiciones de carrera:

concurrency:
  group: ${{ github.workflow }}-${{ github.ref }}
  cancel-in-progress: true

Comparativa: GitHub Actions vs GitLab CI vs Jenkins

CaracterísticaGitHub ActionsGitLab CIJenkins
HospedajeGitHub (SaaS)GitLab o autohospedadoSolo autohospedado
Archivo de config.github/workflows/*.yml.gitlab-ci.ymlJenkinsfile (Groovy)
Tier gratuito2.000 min/mes (privado)400 min/mes (privado)Ilimitado (tu infra)
Marketplace20.000+ actionsLimitado1.800+ plugins
Compuertas de entornoIntegradas (gratis para público)Integradas (Ultimate para reglas)Vía plugins
OIDCAWS, GCP, Azure, CloudflareAWS, GCP, AzureVía plugins
Curva de aprendizajeBajaBajaAlta (DSL Groovy)
Ideal paraProyectos en GitHubMonorepos en GitLabPipelines complejos heredados

Escenario Real: Pipeline Completo

Un pipeline de producción para Node.js con notificaciones Slack:

name: Despliegue Producción

on:
  push:
    branches: [main]

concurrency:
  group: production-deploy
  cancel-in-progress: true

jobs:
  lint:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - uses: actions/setup-node@v4
        with:
          node-version: "22"
          cache: "npm"
      - run: npm ci
      - run: npm run lint

  test:
    needs: lint
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - uses: actions/setup-node@v4
        with:
          node-version: "22"
          cache: "npm"
      - run: npm ci
      - run: npm test -- --coverage

  build:
    needs: test
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - uses: actions/setup-node@v4
        with:
          node-version: "22"
          cache: "npm"
      - run: npm ci
      - run: npm run build
      - uses: actions/upload-artifact@v4
        with:
          name: dist-${{ github.sha }}
          path: dist/
          retention-days: 14

  deploy:
    needs: build
    runs-on: ubuntu-latest
    environment: production
    steps:
      - uses: actions/checkout@v4
      - uses: actions/download-artifact@v4
        with:
          name: dist-${{ github.sha }}
          path: dist/
      - uses: cloudflare/wrangler-action@v3
        with:
          apiToken: ${{ secrets.CF_API_TOKEN }}
          accountId: ${{ secrets.CLOUDFLARE_ACCOUNT_ID }}
      - name: Notificar éxito
        if: success()
        run: |
          curl -s -X POST "${{ secrets.SLACK_WEBHOOK }}" \
            -d "{\"text\":\"Desplegado ${{ github.sha }} por ${{ github.actor }}\"}"
      - name: Notificar fallo
        if: failure()
        run: |
          curl -s -X POST "${{ secrets.SLACK_WEBHOOK }}" \
            -d "{\"text\":\"FALLO de despliegue para ${{ github.sha }}\"}"

Badges de Estado

Agrega un badge de estado en vivo a tu README:

![Deploy](https://github.com/tu-org/tu-repo/actions/workflows/deploy.yml/badge.svg)

Casos Límite y Problemas Comunes

El enmascaramiento de secretos solo funciona para coincidencias exactas. GitHub enmascara el valor literal del secreto en los logs, pero si el secreto está codificado en base64 o URL antes de usarse, el valor original puede aparecer sin enmascarar.

Los cambios en la imagen del runner rompen versiones de herramientas ancladas. GitHub actualiza ubuntu-latest periódicamente. Si tu workflow depende de una versión específica de una herramienta preinstalada, instálala explícitamente con una action de setup.

cancel-in-progress: true cancela la ejecución actual, no la entrante. El nuevo push gana. Esto es casi siempre lo que quieres para despliegues, pero puede sorprenderte en suites de tests largas.

Los secretos de entorno no están disponibles para pull requests de forks. Esto es intencional — los forks no pueden acceder a secretos para evitar filtraciones.

Solución de Problemas

El workflow no se dispara en push a main. Verifica el filtro on.push.branches — el nombre de la rama debe coincidir exactamente. Confirma que el archivo de workflow está en .github/workflows/.

El secreto está disponible pero la autenticación de Wrangler falla. Confirma que el nombre del secreto coincide exactamente (sensible a mayúsculas). Verifica que el token de API de Cloudflare tenga los permisos correctos: Workers Scripts: Edit y Account: Read.

El job espera indefinidamente la aprobación del entorno. Verifica que estás listado como revisor requerido del entorno. Comprueba que el nombre del entorno en el YAML del workflow coincida exactamente con el nombre en Settings.

Resumen

  • Define los workflows como archivos YAML en .github/workflows/ — están versionados junto con tu código
  • Estructura los pipelines como jobs secuenciales: lint → test → build → deploy, usando needs para imponer el orden
  • Usa environment: production con revisores requeridos para compuertas de aprobación humanas
  • Almacena todos los secretos en el almacén de secretos de GitHub — nunca en el archivo YAML
  • Los despliegues en Cloudflare Workers usan cloudflare/wrangler-action; los destinos VPS usan SSH/rsync; IIS usa un runner autohospedado
  • Agrega grupos de concurrency con cancel-in-progress: true para evitar despliegues superpuestos
  • Cachea dependencias con actions/setup-node cache: npm para ejecuciones más rápidas
  • Nombra los artefactos con ${{ github.sha }} para facilitar el rollback re-ejecutando el workflow en cualquier commit anterior

Artículos Relacionados