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 amainy en todas las pull requests (sin filtro de ramas), que es exactamente lo que quieres de una CI.strategy.matrixclona 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@v4trae tu código al runner. Es siempre el primer paso; el@v4fija la versión major de la action.cache: 'npm'en setup-node cachea~/.npmentre 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:
- Nunca aparecen en los logs: GitHub los enmascara automáticamente en cualquier output.
- No se heredan de forks: un workflow disparado desde un pull request externo no accede a tus secrets — protección fundamental contra exfiltración.
- 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) ylatestsolo en la rama por defecto. Nunca confíes enlatestpara 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 (build → deploy), 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.