CI/CD con GitHub Actions
GitHub Actions automatiza el pipeline completo de tu repositorio: cada push puede compilar, pasar lint, ejecutar tests y desplegar a producción. Aprende los conceptos, la estructura YAML y los patrones que usan los proyectos reales.
¿Qué es CI/CD?
CI (Continuous Integration) es la práctica de integrar el código de todo el equipo en el repositorio compartido de forma frecuente. Cada integración se verifica automáticamente con build, lint y tests, de modo que los errores aparecen en minutos, no en días.
CD (Continuous Delivery / Deployment) automatiza la publicación del código verificado. En Continuous Delivery el artefacto queda listo para desplegarse con un clic; en Continuous Deployment se despliega solo a producción cuando el pipeline pasa.
Conceptos de GitHub Actions
Actions se estructura en cinco piezas con una jerarquía clara:
Workflow: el archivo YAML completo en .github/workflows/. Define el autómata: cuándo se dispara y qué hace.
Evento: lo que dispara el workflow. push, pull_request, schedule, workflow_dispatch...
Job: un conjunto de pasos que se ejecutan en un mismo runner. Los jobs de un workflow corren en paralelo por defecto.
Step: cada acción individual dentro de un job: un comando, una acción reutilizable de la marketplace...
Runner: la máquina que ejecuta los jobs. GitHub ofrece runners alojados (ubuntu-latest, macos-latest, windows-latest) y puedes conectar los tuyos.
Estructura del workflow
Todo empieza con un archivo YAML dentro de .github/workflows/. La clave on declara los eventos que lo disparan:
# .github/workflows/ci.yml
name: CI
on:
push:
branches: [main]
pull_request:
branches: [main]
workflow_dispatch:
jobs:
test:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- run: echo "¡Pipeline en marcha!"Con push el workflow corre en cada commit a main; con pull_requesttambién en cada PR contra main, que es donde se filtran los errores antes de integrar. workflow_dispatch añade un botón "Run workflow" manual en la UI de GitHub.
Jobs y steps
Cada job declara en qué runner ejecutarse y una lista de steps que se ejecutan en orden. Los steps pueden ser acciones de la comunidad ( uses) o comandos shell ( run):
jobs:
build:
runs-on: ubuntu-latest
steps:
# Clona el repositorio dentro del runner
- uses: actions/checkout@v4
# Configura Node.js en la máquina
- uses: actions/setup-node@v4
with:
node-version: 22
# Comando shell: instala dependencias
- run: npm ci
# Varios comandos, directorio de trabajo y env propio
- name: Verificar formato
working-directory: apps/web
env:
NODE_ENV: test
run: |
npm run lint
npm testrun se ejecuta en una shell nueva e independiente: las variables y el directorio no persisten entre steps. Usa env: a nivel de step o job para pasar contexto, o actions/upload-artifact para guardar archivos entre jobs.Build, lint y test
El corazón de CI para un proyecto Node/TypeScript: instalar de forma reproducible, compilar, comprobar estilo y ejecutar la suite de tests. Los pasos con nombre hacen legible el pipeline en la UI de GitHub:
jobs:
quality:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: 22
cache: npm
- name: Instalar dependencias
run: npm ci
- name: Typecheck
run: npm run typecheck
- name: Lint
run: npm run lint
- name: Tests
run: npm test
- name: Build
run: npm run buildnpm ci instala exactamente lo que define package-lock.json y borra node_modules antes — es la opción reproducible para CI, a diferencia de npm install.
Secrets
Nunca pongas tokens ni contraseñas en el YAML: se guardan en Settings → Secrets and variables → Actions del repositorio y se referencian en el workflow. GitHub los inyecta como variables de entorno sin exponerlos en los logs:
jobs:
deploy:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Desplegar
env:
VERCEL_TOKEN: ${{ secrets.VERCEL_TOKEN }}
DATABASE_URL: ${{ secrets.DATABASE_URL }}
run: |
npx vercel deploy --prod --token "$VERCEL_TOKEN"
curl -X POST https://api.miapp.com/deploy${{ secrets.API_KEY }} dentro de un run: puede quedar impreso en los logs. Pasa los secretos por env: y redacta la salida.Cache de dependencias
Instalar dependencias cada vez tarda minutos. La acción actions/cache restaura node_modules desde un cache con key derivada del lockfile, y la guarda de nuevo si cambia:
- uses: actions/cache@v4
with:
path: node_modules
key: ${{ runner.os }}-node-${{ hashFiles('package-lock.json') }}
restore-keys: |
${{ runner.os }}-node-La key incluye el hash del lockfile: si las dependencias no cambian, el cache acierta. restore-keys ofrece caches parciales cuando la key falla. setup-node lo tiene integrado con cache: npm para este caso común.
Con pnpm la clave está en que las dependencias van en ~/.local/share/pnpm/store y se usa pnpm fetch para poblar el store antes de instalar:
- uses: pnpm/action-setup@v4
with:
version: 9
- uses: actions/setup-node@v4
with:
node-version: 22
cache: pnpm
- run: pnpm install --frozen-lockfileDespliegue
El último tramo: publicar el artefacto verificado. Según el destino cambia el mecanismo — un CLI con token (Vercel), una API REST (Render), o un push a la rama gh-pages para GitHub Pages:
# Despliegue a Vercel solo desde main
on:
push:
branches: [main]
jobs:
deploy:
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
- name: Deploy a Vercel
env:
VERCEL_TOKEN: ${{ secrets.VERCEL_TOKEN }}
VERCEL_ORG_ID: ${{ secrets.VERCEL_ORG_ID }}
VERCEL_PROJECT_ID: ${{ secrets.VERCEL_PROJECT_ID }}
run: npx vercel deploy --prod --token "$VERCEL_TOKEN"Protege el job de despliegue con una condición needs: solo desplegar si los jobs de calidad han pasado. Y en repositorios con reviewers, el entorno de GitHub ("environment") puede exigir aprobación humana antes del despliegue a producción.
deploy con needs: [quality] hace que el despliegue dependa explícitamente de que build, lint y tests hayan terminado en verde. Sin esa dependencia, los jobs paralelos podrían desplegar un build roto.Matrices
Una matrix ejecuta el mismo job con distintas combinaciones de variables, típicamente para probar varias versiones de Node o sistemas operativos. Cada combinación es un job independiente y paralelo:
jobs:
test:
runs-on: ubuntu-latest
strategy:
matrix:
node-version: [18, 20, 22]
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: ${{ matrix.node-version }}
cache: npm
- run: npm ci
- run: npm testPuedes combinar dimensiones: os: [ubuntu-latest, macos-latest] con node-version: [20, 22] genera 4 jobs. Para versiones futuras, include añade configuraciones extra y exclude descarta combinaciones que no quieres.
Ejercicios
Con GitHub Actions tu repositorio deja de ser código inerte: cada push se comprueba, se prueba y se publica de forma reproducible. Los patrones de este tutorial — jobs separados, cache, secrets, matrices — son los mismos bloques con los que se construyen pipelines de cualquier complejidad.