GitHub Actions: Ejemplos de Workflows CI/CD y Matriz
Cómo construir y desplegar con GitHub Actions usando workflows, matrices, caché, secrets, environments y workflows reutilizables.
Visión general
Los equipos que todavía verifican el código a mano — ejecutando tests en local, construyendo artefactos en la máquina de un desarrollador y desplegando por SSH — entregan despacio y rompen cosas. GitHub Actions convierte cada evento de Git en un workflow automatizado y reproducible que corre en entornos aislados, de modo que el pipeline vive junto al código que prueba.
Al estar el pipeline definido en YAML dentro del repositorio, obtiene de regalo control de versiones, revisión por pares y auditabilidad. Un check verde en un pull request significa que el código compila, los tests pasan y las reglas de estilo se cumplen antes de que nadie haga merge.
Esta receta reúne los patrones de workflow que realmente copias en .github/workflows/: un pipeline CI de Node.js, matrices de build, jobs programados, secrets y environments, caché de dependencias y workflows reutilizables. Para el recorrido conceptual completo, consulta la guía de CI/CD con GitHub Actions.
Cuándo usarlo
Usa esta receta cuando:
- Detectas regresiones en cada pull request, antes de que lleguen a producción — consulta Unit Testing para automatización de tests.
- Publicas imágenes Docker desde tags de release para que los despliegues sean inmutables — consulta Docker Basics para la construcción de imágenes.
- Despliegas a staging o producción con aprobaciones controladas. Consulta Environment Variables para gestionar la configuración de despliegue.
- Haces chequeos de linting y formateo antes del merge. Consulta Pre-Commit Hooks para calidad de código en local.
- Arrancas trabajo recurrente — auditorías de dependencias, copias de seguridad nocturnas — con una planificación cron en UTC.
- Analizas vulnerabilidades en las dependencias en cada commit.
Descarta GitHub Actions cuando necesites runners con GPU o agentes de build poco comunes, cuando tu código viva fuera de GitHub (usa GitLab CI o Jenkins en su lugar), o cuando el coste en minutos hosted por build supere lo que costaría un runner self-hosted a tu volumen de commits.
Solución
Workflow básico (Node.js CI)
Este workflow se ejecuta en cada push y pull request dirigido a main. Baja el código, prepara Node.js, instala las dependencias y recorre todo el pipeline de validación.
# .github/workflows/ci.yml
name: CI
# Trigger: ejecutar en push y PR a la rama main
on:
push:
branches: [main]
pull_request:
branches: [main]
jobs:
build:
runs-on: ubuntu-latest
steps:
# Descarga el código del repositorio
- uses: actions/checkout@v4
# Configura Node.js con caché de npm integrado
- uses: actions/setup-node@v4
with:
node-version: '20'
cache: 'npm'
# Instala dependencias usando el lock file para reproducibilidad
- run: npm ci
# Ejecuta linting para detectar problemas de estilo temprano
- run: npm run lint
# Ejecuta la suite de tests
- run: npm run test
# Construye el artefacto de producción
- run: npm run build
Matriz de build
Una estrategia de matriz ejecuta el mismo job en combinaciones de SO y versiones del runtime. Detecta bugs específicos de plataforma y confirma la compatibilidad sin duplicar código del workflow. Consulta Matriz de estrategia en GitHub Actions para combinaciones con include/exclude y matrices dinámicas.
jobs:
test:
strategy:
matrix:
os: [ubuntu-latest, windows-latest, macos-latest]
node: [18, 20, 22]
runs-on: ${{ matrix.os }}
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: ${{ matrix.node }}
- run: npm ci
- run: npm test
Workflows programados (Cron)
El trigger schedule ejecuta workflows con una planificación cron en UTC — útil para auditorías de dependencias, builds nocturnos o tareas de limpieza. Añade workflow_dispatch junto a él para poder lanzar el job manualmente.
# .github/workflows/nightly-audit.yml
name: Nightly Audit
on:
schedule:
- cron: '0 3 * * *' # Todos los días a las 03:00 UTC
workflow_dispatch: # Permite ejecuciones manuales desde la pestaña Actions
jobs:
audit:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: '20'
cache: 'npm'
- run: npm ci
- run: npm audit --audit-level=high
Deploy con Secrets
Este job solo se ejecuta después de que el job test tenga éxito y solo en la rama main. Los secrets se inyectan en runtime desde la configuración del repositorio y quedan enmascarados en los logs.
jobs:
deploy:
runs-on: ubuntu-latest
needs: test # espera a que el job test pase
if: github.ref == 'refs/heads/main'
steps:
- uses: actions/checkout@v4
- name: Deploy to production
run: |
echo "Deploying with token"
curl -X POST https://api.example.com/release \
-H "Authorization: Bearer ${{ secrets.DEPLOY_TOKEN }}"
secrets y vars se comportan de forma distinta — elige el adecuado para cada valor:
| Contexto | Cifrado en reposo | Enmascarado en logs | Úsalo para |
|---|---|---|---|
secrets.* | Sí | Sí | Tokens de API, credenciales de deploy, claves de firma |
vars.* | No | No | Feature flags, URLs de entorno, configuración no sensible |
Caché de dependencias
La caché almacena dependencias descargadas entre ejecuciones del workflow. La clave incluye el SO y un hash del lock file, así que se invalida automáticamente cuando cambian las dependencias.
steps:
- uses: actions/checkout@v4
- uses: actions/cache@v4
with:
path: |
~/.npm
node_modules
key: ${{ runner.os }}-node-${{ hashFiles('**/package-lock.json') }}
- run: npm ci
Workflow reutilizable
Cuando varios repositorios necesitan la misma lógica de pipeline, extráela a un workflow reutilizable. Quienes lo invocan pasan inputs y reciben outputs, y la lógica compartida vive en un solo lugar. Consulta Workflows reutilizables en GitHub Actions para secrets: inherit, paso de outputs y estrategias de versionado.
# .github/workflows/reusable-lint.yml
name: Reusable Lint
on:
workflow_call:
inputs:
node-version:
required: true
type: string
jobs:
lint:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: ${{ inputs.node-version }}
- run: npm ci
- run: npm run lint
Llámalo desde otro repositorio:
jobs:
call-lint:
uses: org/shared-workflows/.github/workflows/reusable-lint.yml@main
with:
node-version: '20'
Explicación
Cómo funciona el modelo de ejecución
Un workflow es un archivo YAML que se dispara con un evento (push, pull_request, schedule, workflow_dispatch o una llamada externa a repository_dispatch). GitHub encola cada job del workflow y lo asigna a un runner — una VM hosted (ubuntu-latest, windows-latest, macos-latest) o una máquina que operas tú (self-hosted). Dentro de un job, los steps corren secuencialmente en la misma VM, compartiendo filesystem y entorno.
Los jobs corren en paralelo por defecto. needs convierte el grafo en una secuencia: un job espera a que sus dependencias terminen con éxito antes de arrancar. Las condiciones if expresan ramas encima — por ejemplo, desplegar solo cuando la ref es main:
Contextos y expresiones
Todo lo que va dentro de ${{ }} es una expresión evaluada contra un contexto. Los que más usarás: github (payload del evento, ref, SHA), secrets (valores cifrados), vars (configuración en claro), matrix (la combinación actual de la matriz), inputs (valores pasados por quien invoca en workflows reutilizables y dispatch) y env / runner para datos del entorno y la máquina. Las expresiones funcionan en comandos run, condiciones if e inputs with.
Permisos y GITHUB_TOKEN
Cada workflow corre con un GITHUB_TOKEN cuyos permisos por defecto vienen de la configuración del repositorio. Deja el workflow en permissions: read-all y otorga contents: write o packages: write solo a los jobs que lo necesiten — una action de terceros comprometida solo puede hacer lo que el token permite. Para despliegues en la nube, prefiere federación OIDC sobre secrets de larga duración: el runner intercambia un JWT efímero por un rol en la nube, así que no hay ningún AWS_ACCESS_KEY_ID guardado en Secrets.
Concurrencia y facturación
Un grupo de concurrency cancela la ejecución anterior cuando llega un push nuevo a la misma ref, lo que evita que despliegues obsoletos compitan con los nuevos:
concurrency:
group: deploy-${{ github.ref }}
cancel-in-progress: true
Los minutos de runners hosted se facturan por SO: los de Linux cuentan 1x, los de Windows 2x y los de macOS 10x. Una matriz de 3 SOs × 3 runtimes produce 9 jobs por commit, así que el tamaño de la matriz tiene un precio directo en repositorios privados — consulta la facturación de GitHub Actions.
Variantes
Workflow reutilizable con estrategia de matriz
# .github/workflows/test-matrix.yml
name: Test Matrix
on:
workflow_call:
inputs:
node-version:
required: false
type: string
default: "20"
jobs:
test:
runs-on: ${{ matrix.os }}
strategy:
fail-fast: false # No cancelar otros jobs en fallo
matrix:
os: [ubuntu-latest, windows-latest, macos-latest]
node: [18, 20, 22]
exclude:
- os: macos-latest
node: 18 # Omitir Node viejo en macOS
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: ${{ matrix.node }}
cache: 'npm'
- run: npm ci
- run: npm test -- --coverage
- uses: codecov/codecov-action@v4
if: matrix.os == 'ubuntu-latest' && matrix.node == 20
Reglas de protección de environments
# .github/workflows/deploy.yml
name: Deploy
on:
push:
tags: ['v*']
jobs:
deploy:
runs-on: ubuntu-latest
environment:
name: production
url: https://myapp.example.com
# Los revisores requeridos deben aprobar antes del deploy
# Se configura en repo Settings > Environments
steps:
- uses: actions/checkout@v4
- name: Deploy to production
run: |
echo "Deploying ${{ github.ref_name }} to production"
./deploy.sh --env prod --version ${{ github.ref_name }}
env:
DEPLOY_TOKEN: ${{ secrets.DEPLOY_TOKEN }}
Action compuesta
# .github/actions/setup-project/action.yml
name: Setup Project
description: Install dependencies and cache them
inputs:
node-version:
required: false
default: '20'
runs:
using: composite
steps:
- uses: actions/setup-node@v4
with:
node-version: ${{ inputs.node-version }}
cache: 'npm'
- name: Install dependencies
shell: bash
run: npm ci
- name: Build
shell: bash
run: npm run build
# Uso en un workflow
jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: ./.github/actions/setup-project
with:
node-version: '22'
- run: npm test
Jobs y steps condicionales
jobs:
lint:
runs-on: ubuntu-latest
if: github.event_name == 'pull_request'
steps:
- uses: actions/checkout@v4
- run: npm run lint
deploy:
needs: [lint, test]
if: github.ref == 'refs/heads/main' && success()
runs-on: ubuntu-latest
steps:
- run: echo "Deploying only on main after lint+test pass"
Resumen de job para comentarios de PR
$GITHUB_STEP_SUMMARY renderiza Markdown en la vista de la ejecución de Actions — una forma rápida de mostrar resultados sin un comentario de bot. Las duraciones aquí son ilustrativas; en una ejecución real escribirías los valores medidos.
- name: Generate summary
if: always()
run: |
echo "## Test Results" >> $GITHUB_STEP_SUMMARY
echo "| Suite | Status |" >> $GITHUB_STEP_SUMMARY
echo "|-------|--------|" >> $GITHUB_STEP_SUMMARY
echo "| Unit | ${{ job.status }} |" >> $GITHUB_STEP_SUMMARY
echo "| E2E | ${{ job.status }} |" >> $GITHUB_STEP_SUMMARY
Lo que funciona
- Fija las versiones de las actions con el SHA completo del commit para seguridad de la supply chain. Los tags como
v4son cómodos, pero un maintainer comprometido podría reapuntar el tag a código malicioso; un SHA ejecuta exactamente el mismo código siempre. - Ejecuta
npm ci, nonpm install—ciinstala exactamente lo que fijapackage-lock.json, así que el build no puede derivar entre la máquina de un desarrollador y el runner. - Separa los jobs: test, build y deploy como jobs distintos con dependencias explícitas (
needs). Los fallos quedan aislados, los jobs corren en paralelo y los logs se leen mejor. - Usa grupos de concurrencia para cancelar ejecuciones obsoletas cuando se suben commits nuevos. Ahorra minutos de CI y evita que despliegues antiguos pisen a los más recientes.
- Guarda los secrets en GitHub Secrets, nunca escribas tokens en los archivos de workflow. Los secrets están cifrados en reposo y enmascarados en los logs. Incluso los repositorios privados pueden filtrarse a través de PRs de forks.
- Extrae la lógica compartida del pipeline a workflows reutilizables — quienes los invocan desde otros repos pasan inputs, y una corrección en el repositorio central llega a todos los consumidores de golpe.
- Falla rápido con permisos estrictos: define
permissions: read-alla nivel de workflow y concede write solo donde haga falta — una action comprometida no puede hacer más de lo que el token permite. - Vigila la factura de Actions — configura un límite de gasto y revisa el uso cada mes. Una matriz de 3 SOs y 3 versiones de Node produce 9 jobs por commit, y los minutos de macOS cuentan 10x.
- Añade un fallback con restore-keys a
actions/cachepara que una caché vieja siga acelerandonpm cicuando cambie el lock file:
- uses: actions/cache@v4
with:
path: ~/.npm
key: ${{ runner.os }}-npm-${{ hashFiles('**/package-lock.json') }}
restore-keys: |
${{ runner.os }}-npm-
- Usa runners self-hosted para builds pesados cuando la cola o la CPU hosted sean el cuello de botella:
runs-on: self-hosted # Tu propio hardware
- Divide la suite de tests entre runners con la matriz para paralelizar suites largas:
strategy:
matrix:
shard: [1, 2, 3, 4]
steps:
- run: npm test -- --shard=${{ matrix.shard }}/4
- Activa la caché de capas de Docker para acelerar los builds de imágenes:
- uses: docker/build-push-action@v5
with:
cache-from: type=gha
cache-to: type=gha,mode=max
Errores comunes
- Usar
actions/checkoutsin especificar un ref, causando problemas de detached HEAD o descargando el commit equivocado cuando se dispara por un tag. Usaref: ${{ github.ref }}explícitamente cuando haga falta. - Desplegar en cada push en lugar de filtrar por rama o tag — ramas de funcionalidad sin terminar acaban en producción y tu proceso de release queda anulado por completo.
- No cachear las dependencias, generando builds que se descargan internet en cada ejecución. Una caché bien ajustada reduce el tiempo de instalación de minutos a segundos.
- Acudir a
ubuntu-latestcuando necesitas un SO concreto para compilación nativa. La etiquetalatestmigra con el tiempo — fijaubuntu-22.04y el build se mantiene reproducible. - Steps de notificación que omiten
if: failure()— las alertas de Slack o email solo saltan cuando el pipeline está verde, que es justo cuando no las necesitas. - Valores de entorno escritos en el propio workflow en lugar de variables de repositorio — cada cambio de URL o versión se convierte en una edición de YAML y un commit.
permissions: write-ally otras concesiones amplias — una action de terceros comprometida puede reescribir tu código, crear releases o tocar secrets. El menor privilegio aquí no es opcional.
Lectura adicional
- Sintaxis de workflows de GitHub Actions — la referencia completa de
on:/jobs:/steps:. - Uso de secrets en GitHub Actions — secrets de repositorio vs environment y enmascarado.
- Environments y reglas de protección — revisores requeridos, ramas y URLs de entorno.
- Reutilización de workflows —
workflow_call, inputs, outputs y herencia de secrets. - Workflows ejecutables del companion — los YAML de esta receta listos para copiar a un repositorio.
Preguntas frecuentes
¿Cómo ejecuto un workflow manualmente?
Añade workflow_dispatch: al bloque on:. Aparece entonces en la pestaña Actions como una ejecución manual, con inputs opcionales que el workflow puede leer.
¿Puedo reutilizar workflows entre repositorios?
Sí. Crea un workflow con on: workflow_call en un repositorio central y referéncialo desde otros con la forma uses: org/shared-workflows/.github/workflows/reusable.yml@main. La lógica compartida se mantiene actualizada en un solo lugar.
¿Cómo depuro un workflow que falla?
Usa la action tmate para abrir una sesión SSH interactiva dentro del runner, o añade set -x en los steps de shell para una salida detallada. También puedes habilitar el debug de steps poniendo el secret ACTIONS_STEP_DEBUG a true.
¿Cómo evito que los workflows se ejecuten desde forks?
Usa if: github.event.pull_request.head.repo.full_name == github.repository para omitir workflows disparados por forks externos. Para workflows que tocan secrets o producción, ve más lejos: desactiva los triggers de PR de forks en la configuración del repositorio y exige aprobación manual.
¿Cuál es la diferencia entre workflow_dispatch y repository_dispatch?
workflow_dispatch es el trigger manual — la pestaña Actions o la API. repository_dispatch solo existe para quien llama desde fuera: un servicio de terceros golpea el endpoint de la API cuando necesita un build que ningún evento de Git puede arrancar.
¿Cómo comparto secrets entre entornos?
Usa secrets a nivel de repositorio para valores no sensibles y secrets a nivel de environment para credenciales de producción. Haz referencia a ellos con ${{ secrets.SECRET_NAME }}:
steps:
- run: deploy.sh
env:
API_KEY: ${{ secrets.PROD_API_KEY }} # Secret de environment
LOG_LEVEL: ${{ vars.LOG_LEVEL }} # Variable de repositorio
¿Cómo ejecuto jobs en secuencia o en paralelo?
Por defecto, los jobs corren en paralelo. Usa needs para crear dependencias:
jobs:
build: # Corre primero
runs-on: ubuntu-latest
test: # Corre después de build
needs: build
runs-on: ubuntu-latest
deploy: # Corre después de test
needs: test
runs-on: ubuntu-latest
¿Cómo paso artefactos entre jobs?
Usa actions/upload-artifact y actions/download-artifact:
build:
runs-on: ubuntu-latest
steps:
- run: npm run build
- uses: actions/upload-artifact@v4
with:
name: dist
path: dist/
deploy:
needs: build
runs-on: ubuntu-latest
steps:
- uses: actions/download-artifact@v4
with:
name: dist
path: dist/
- run: ./deploy.sh dist/
Recursos Relacionados
Testear a Través de Múltiples OS y Versiones con GitHub
Cómo usar GitHub Actions matrix strategy para testear a través de múltiples sistemas operativos, versiones de lenguajes y configuraciones con include, exclude y matrices dinámicas.
RecipeCompartir Lógica de Workflows con GitHub Actions
Cómo crear y consumir reusable workflows en GitHub Actions, cubriendo inputs, secrets, jobs condicionales, matrix strategy y sharing a nivel organización.
GuideGuía completa de CI/CD con GitHub Actions
Construye pipelines CI/CD desde cero con GitHub Actions. Cubre workflows, runners, builds en matriz, caché, secretos, entornos, estrategias de despliegue y workflows reutilizables.
RecipeConfiguración de Pipelines CI/CD
Configura pipelines CI/CD automatizados para testing, building y deployment de aplicaciones con GitHub Actions y lo que funciona.
RecipeVariables de Entorno
Cómo leer, establecer y gestionar variables de entorno de forma segura en Python, JavaScript y Java.
RecipeFundamentos de Docker
Cómo containerizar una aplicación, escribir un Dockerfile y ejecutar contenedores con Docker Compose.