github actionsci cddevopsautomatizacion

GitHub Actions explicado: CI, deploy a Vercel e imágenes Docker en GHCR

Cómo funcionan los workflows de GitHub Actions: triggers, matriz de versiones de Node, secrets, permisos mínimos y tres plantillas listas para producción.

23 de agosto de 2026·8 min de lectura

GitHub Actions es el sistema de CI/CD que ya tienes sin instalar nada: vive dentro de tu repositorio, se dispara con cada push o pull request y ejecuta cualquier cosa que quepa en un contenedor. La barrera de entrada es entender la anatomía del YAML — una vez la dominas, escribir un workflow nuevo son minutos.

Anatomía mínima: un CI para Node

name: CI

on:
  push:
    branches: [main]
  pull_request:

jobs:
  build:
    runs-on: ubuntu-latest
    strategy:
      matrix:
        node-version: [20, 22]
    steps:
      - uses: actions/checkout@v4
      - name: Use Node.js ${{ matrix.node-version }}
        uses: actions/setup-node@v4
        with:
          node-version: ${{ matrix.node-version }}
          cache: 'npm'
      - name: Install dependencies
        run: npm ci
      - name: Lint
        run: npm run lint --if-present
      - name: Type check
        run: tsc --noEmit --if-present
      - name: Build
        run: npm run build --if-present

Cada pieza tiene su función:

  • on: define los disparadores. Este workflow corre en pushes a main y en todas las pull requests (sin filtro de ramas), que es exactamente lo que quieres de una CI.
  • strategy.matrix clona el job por cada combinación: aquí el mismo build corre contra Node 20 y Node 22 en paralelo. Si algo rompe solo en una versión, lo sabes antes del merge.
  • actions/checkout@v4 trae tu código al runner. Es siempre el primer paso; el @v4 fija la versión major de la action.
  • cache: 'npm' en setup-node cachea ~/.npm entre ejecuciones. Sin esto, cada CI descarga todas las dependencias desde cero.

El sufijo --if-present evita que falle si tu proyecto no define ese script — útil para workflows genéricos compartidos entre varios repos (nota: es un flag nativo de npm; con yarn o pnpm conviene verificar el soporte de tu versión).

Los runners y el coste real

runs-on: ubuntu-latest te da una máquina efímera de 4 vCPUs y 16 GB de RAM que vive exactamente lo que dura el job. En repositorios públicos es gratis ilimitado; en privados hay cuota mensual de minutos (2000 en el plan gratuito) con multiplicadores según SO — Linux 1x, Windows 2x, macOS 10x. Para un CI estándar de Node, esa cuota da para cientos de ejecuciones mensuales.

Secrets: cómo inyectar credenciales sin commitearlas

Los secrets se configuran en Settings → Secrets and variables → Actions y llegan al workflow como ${{ secrets.NOMBRE }}. Reglas que debes conocer:

  1. Nunca aparecen en los logs: GitHub los enmascara automáticamente en cualquier output.
  2. No se heredan de forks: un workflow disparado desde un pull request externo no accede a tus secrets — protección fundamental contra exfiltración.
  3. Un secret no es legible, solo usable: puedes escribirlo en un archivo o pasarlo como variable de entorno, pero jamás volver a leerlo desde la UI.

Este es el patrón de despliegue a Vercel desde Actions:

jobs:
  deploy:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - uses: actions/setup-node@v4
        with:
          node-version: 22
          cache: 'npm'
      - name: Pull Vercel environment
        run: npx vercel@latest pull --yes --environment=production --token=${{ secrets.VERCEL_TOKEN }}
      - name: Build project
        run: npx vercel@latest build --prod --token=${{ secrets.VERCEL_TOKEN }}
      - name: Deploy to Vercel
        run: npx vercel@latest deploy --prebuilt --prod --token=${{ secrets.VERCEL_TOKEN }}

La secuencia pull → build → deploy prebuilt construye en el runner de GitHub y sube solo el artefacto final. El token se crea en Vercel (Account Settings → Tokens) con scope limitado al proyecto.

Publicar imágenes Docker en GHCR

El GitHub Container Registry (ghcr.io) guarda imágenes Docker junto a tu código, con visibilidad ligada al repo. El workflow completo:

env:
  REGISTRY: ghcr.io
  IMAGE_NAME: ${{ github.repository }}

jobs:
  build-and-push:
    runs-on: ubuntu-latest
    permissions:
      contents: read
      packages: write
    steps:
      - uses: actions/checkout@v4
      - name: Set up Docker Buildx
        uses: docker/setup-buildx-action@v3
      - name: Log in to GHCR
        uses: docker/login-action@v3
        with:
          registry: ${{ env.REGISTRY }}
          username: ${{ github.actor }}
          password: ${{ secrets.GITHUB_TOKEN }}
      - name: Extract metadata
        id: meta
        uses: docker/metadata-action@v5
        with:
          images: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}
          tags: |
            type=ref,event=branch
            type=sha,prefix=
            type=raw,value=latest,enable={{is_default_branch}}
      - name: Build and push
        uses: docker/build-push-action@v6
        with:
          context: .
          push: true
          tags: ${{ steps.meta.outputs.tags }}
          labels: ${{ steps.meta.outputs.labels }}
          cache-from: type=gha
          cache-to: type=gha,mode=max

Tres detalles que separan este workflow de un tutorial básico:

  • permissions: mínimo: el job declara explícitamente que solo lee contenido y escribe paquetes. Desde que GitHub activó permisos restringidos por defecto, declararlos es buena práctica de seguridad (y requisito en organizaciones con políticas estrictas).
  • ${{ secrets.GITHUB_TOKEN }} no se configura: GitHub lo genera por ejecución y lo rota automáticamente. Es el token interno del repo, distinto de un Personal Access Token.
  • Estrategia de tags: rama actual (main), hash corto del commit (trazabilidad total: cada imagen apunta a su commit exacto) y latest solo en la rama por defecto. Nunca confíes en latest para desplegar; usa el tag SHA.
  • cache-from/to: type=gha: las capas Docker se cachean en la infraestructura de Actions; rebuilds posteriores reutilizan capas intactas.

Errores frecuentes al empezar

Workflow que no se dispara: casi siempre es el filtro de ramas. branches: [main] no dispara en pushes a otras ramas — si desarrollas en feat/x, el trigger será la PR al abrirse.

YAML inválido por interpolación: ${{ }} dentro de strings necesita cuidado con las comillas; si el editor no resalta sintaxis YAML, pégalo en un validador antes de pushear (un workflow roto por sintaxis no avisa: simplemente no corre).

Jobs dependientes: por defecto todos los jobs corren en paralelo. Si necesitas secuencia (builddeploy), usa needs: build en el segundo job.

Genera tus workflows por plantilla

CI con matriz de Node, deploy a Vercel o publicación en GHCR: nuestro generador de GitHub Actions produce los tres workflows completos eligiendo gestor de paquetes y triggers (push, PR, manual) mediante toggles.

Preguntas frecuentes

¿Actions sustituye a Jenkins o GitLab CI? Para proyectos alojados en GitHub, sí en la práctica: integración nativa, marketplace enorme y cero infraestructura que mantener. Jenkins sigue vivo en empresas con requisitos muy particulares on-premise.

¿Puedo correr tests de navegador? Sí, con Playwright o Cypress directamente sobre el runner Ubuntu, o usando contenedores de servicios (services: en el job) para bases de datos durante los tests.

¿Cómo depuro un workflow que falla? Añade un paso temporal con run: ls -la && cat package.json para inspeccionar el estado del runner, o usa tmate.io (action mxschmitt/action-tmate) para abrir una sesión SSH interactiva dentro del runner.


Genera tu workflow listo para copiar con el generador de GitHub Actions, gratis y directamente en tu navegador.

Pruébalo sin código

GitHub Actions

CI/CD listo para pegar.

Abrir GitHub Actions

Hecho por

Miguel Ángel Colorado Marin (MACM)

Full-Stack Developer · Guadalajara, España

Desarrollo aplicaciones web, herramientas digitales y proyectos completos — desde el diseño hasta el despliegue.

Contáctame