Lo que aprenderas en este tutorial
- Conceptos basicos de GitHub Actions
- Como escribir archivos de workflow
- Ejecucion automatica de tests
- Build y guardado de artefactos
- Configuracion de despliegue por ambiente
- Gestion de secrets y seguridad
Requisitos previos: Tener cuenta de GitHub. Conocimiento basico de proyectos Node.js facilitara la comprension.
Que es CI/CD y por que es necesario
Historia de CI/CD
El concepto de Integracion Continua (CI) fue sistematizado en 2000 por Martin Fowler y Kent Beck.
“La integracion continua es una practica de desarrollo de software donde los miembros del equipo integran su trabajo frecuentemente” - Martin Fowler
Entrega/Despliegue Continuo (CD) extiende CI para automatizar los releases a produccion.
Evolucion de las herramientas CI/CD
| Ano | Herramienta | Caracteristicas |
|---|---|---|
| 2004 | Hudson/Jenkins | On-premise, basado en plugins |
| 2011 | Travis CI | Basado en cloud, integracion GitHub |
| 2014 | CircleCI | Soporte Docker, builds rapidos |
| 2017 | GitLab CI | Integrado en GitLab |
| 2019 | GitHub Actions | Integrado en GitHub, marketplace |
Ventajas de GitHub Actions
- Integrado en GitHub: Sin servicios adicionales, experiencia fluida
- Definicion YAML: Infraestructura como codigo (IaC)
- Marketplace: Abundantes actions reutilizables
- Nivel gratuito: Ilimitado para repositorios publicos
- Matrix builds: Tests paralelos en multiples entornos
DORA Metrics (Metricas DevOps)
Segun la investigacion de Google (DORA), los equipos de alto rendimiento logran:
| Metrica | Elite | Bajo rendimiento |
|---|---|---|
| Frecuencia de despliegue | Varias veces/dia | Menos de 1/mes |
| Lead time | Menos de 1 hora | Mas de 1 mes |
| Tasa de fallo | 0-15% | 46-60% |
| Tiempo de recuperacion | Menos de 1 hora | Mas de 1 semana |
CI/CD es un elemento importante para mejorar estas metricas.
Documentacion oficial: GitHub Actions Documentation
Step 1: Conceptos basicos de GitHub Actions
GitHub Actions funciona colocando archivos YAML en el directorio .github/workflows/ del repositorio.
Estructura basica
# Nombre del workflow
name: CI Pipeline
# Trigger (cuando ejecutar)
on:
push:
branches: [main]
pull_request:
branches: [main]
# Definicion de jobs
jobs:
job-name:
runs-on: ubuntu-latest
steps:
- name: Step 1
run: echo "Hello"
Conceptos principales
| Concepto | Descripcion | Ejemplo |
|---|---|---|
| Workflow | Proceso de automatizacion definido en YAML | CI, despliegue |
| Event | Trigger que inicia el workflow | push, pull_request, schedule |
| Job | Conjunto de steps ejecutados en el mismo runner | test, build, deploy |
| Step | Tareas individuales | checkout, npm install |
| Action | Unidad reutilizable de tareas | actions/checkout@v4 |
| Runner | Servidor que ejecuta el workflow | ubuntu-latest, windows-latest |
Ciclo de vida del workflow
flowchart LR
Event["Evento ocurre"] --> Workflow["Workflow inicia"] --> Job["Job ejecuta<br/>(paralelo/secuencial)"] --> Step["Step ejecuta"] --> Done["Completado"]
subgraph Events["Tipos de evento"]
direction TB
E1["push"]
E2["pull_request"]
E3["schedule (cron)"]
E4["workflow_dispatch (manual)"]
E5["repository_dispatch (API)"]
end
Events -.-> Event
Step 2: Crear el primer workflow
Estructura de directorios
your-project/
├── .github/
│ └── workflows/
│ └── ci.yml
├── src/
├── package.json
└── README.md
.github/workflows/ci.yml
name: CI
on:
push:
branches: [main, develop]
pull_request:
branches: [main]
jobs:
test:
runs-on: ubuntu-latest
steps:
# Checkout del repositorio
- name: Checkout repository
uses: actions/checkout@v4
# Setup de Node.js
- name: Setup Node.js
uses: actions/setup-node@v4
with:
node-version: '20'
cache: 'npm'
# Instalar dependencias
- name: Install dependencies
run: npm ci
# Ejecutar Lint
- name: Run linter
run: npm run lint
# Ejecutar tests
- name: Run tests
run: npm test
# Subir reporte de cobertura
- name: Upload coverage
uses: codecov/codecov-action@v3
with:
files: ./coverage/lcov.info
Diferencia entre npm ci y npm install
| Comando | Uso | Caracteristicas |
|---|---|---|
npm ci | Entorno CI | Usa package-lock.json estrictamente, rapido |
npm install | Entorno desarrollo | Prioriza package.json, puede actualizar lock |
Mejores practicas: Usa siempre
npm cien entornos CI.
Step 3: Matrix builds
Ejecuta tests paralelos en multiples versiones de Node.js y sistemas operativos.
name: Matrix Build
on: [push, pull_request]
jobs:
test:
runs-on: ${{ matrix.os }}
strategy:
matrix:
os: [ubuntu-latest, windows-latest, macos-latest]
node-version: [18, 20, 22]
fail-fast: false # Continuar aunque uno falle
steps:
- uses: actions/checkout@v4
- name: Setup Node.js ${{ matrix.node-version }}
uses: actions/setup-node@v4
with:
node-version: ${{ matrix.node-version }}
cache: 'npm'
- run: npm ci
- run: npm test
Exclusion e inclusion en matrix
strategy:
matrix:
os: [ubuntu-latest, windows-latest]
node-version: [18, 20]
# Excluir combinaciones especificas
exclude:
- os: windows-latest
node-version: 18
# Agregar combinaciones
include:
- os: ubuntu-latest
node-version: 22
experimental: true
Step 4: Build y guardado de artefactos
name: Build and Upload
on:
push:
branches: [main]
jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Setup Node.js
uses: actions/setup-node@v4
with:
node-version: '20'
cache: 'npm'
- name: Install and Build
run: |
npm ci
npm run build
# Subir artefactos de build
- name: Upload build artifacts
uses: actions/upload-artifact@v4
with:
name: build-output
path: dist/
retention-days: 7
# Usar artefactos en el siguiente job
deploy:
needs: build
runs-on: ubuntu-latest
steps:
- name: Download artifacts
uses: actions/download-artifact@v4
with:
name: build-output
- name: Deploy
run: |
echo "Deploying..."
ls -la
Step 5: Variables de entorno y secrets
Como configurar secrets
- Repositorio GitHub -> Settings -> Secrets and variables -> Actions
- Click en “New repository secret”
- Ingresar nombre (ej:
DEPLOY_TOKEN) y valor
Tipos de secrets
| Tipo | Alcance | Uso |
|---|---|---|
| Repository secrets | Repositorio unico | API keys, tokens |
| Environment secrets | Solo ambiente especifico | Para produccion/staging |
| Organization secrets | Todos los repos de la org | Cuentas de servicio comunes |
name: Deploy with Secrets
on:
push:
branches: [main]
jobs:
deploy:
runs-on: ubuntu-latest
environment: production # Especificar environment (permite flujo de aprobacion)
steps:
- uses: actions/checkout@v4
# Usar secrets como variables de entorno
- name: Deploy
env:
API_KEY: ${{ secrets.API_KEY }}
DEPLOY_TOKEN: ${{ secrets.DEPLOY_TOKEN }}
run: |
echo "Deploying with API key..."
./scripts/deploy.sh
# Token automatico proporcionado por GitHub
- name: Create Release
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
run: |
gh release create v1.0.0
Mejores practicas de seguridad
- Principio de minimo privilegio: Otorgar solo permisos necesarios
- Rotacion de secrets: Actualizar periodicamente
- Prevencion de exposicion en logs: Los secrets se enmascaran automaticamente
- Restriccion de acceso desde forks: Limitar acceso a secrets en PRs
# Establecer permisos explicitamente
permissions:
contents: read
packages: write
id-token: write # Para autenticacion OIDC
Nota de seguridad: Los secrets se enmascaran automaticamente como
***en logs, pero aun asi ten cuidado de no exponerlos accidentalmente.
Step 6: Condiciones y filtros
name: Conditional Workflow
on:
push:
branches: [main, develop]
# Ejecutar solo cuando cambian paths especificos
paths:
- 'src/**'
- 'package.json'
# Excluir paths especificos
paths-ignore:
- '**.md'
- 'docs/**'
jobs:
test:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- run: npm test
deploy-staging:
needs: test
runs-on: ubuntu-latest
# Ejecutar solo en rama develop
if: github.ref == 'refs/heads/develop'
steps:
- run: echo "Deploying to staging..."
deploy-production:
needs: test
runs-on: ubuntu-latest
# Solo en rama main con tag
if: github.ref == 'refs/heads/main' && startsWith(github.ref, 'refs/tags/')
steps:
- run: echo "Deploying to production..."
notify-on-failure:
needs: [test]
runs-on: ubuntu-latest
# Ejecutar solo si fallo
if: failure()
steps:
- name: Notify Slack
run: echo "Tests failed!"
Ejemplos de expresiones condicionales
# Nombre de rama
if: github.ref == 'refs/heads/main'
# Tipo de evento
if: github.event_name == 'pull_request'
# Actor
if: github.actor == 'dependabot[bot]'
# Condicion compuesta
if: github.ref == 'refs/heads/main' && github.event_name == 'push'
# Basado en resultado
if: success() # Job anterior exitoso
if: failure() # Job anterior fallo
if: always() # Siempre ejecutar
if: cancelled() # Al cancelar
Step 7: Uso de cache
Reduce tiempo de build con cache de dependencias.
name: Build with Cache
on: [push]
jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Setup Node.js
uses: actions/setup-node@v4
with:
node-version: '20'
cache: 'npm' # Cache automatico
# Gestionar cache manualmente
- name: Cache node modules
id: cache-npm
uses: actions/cache@v4
with:
path: node_modules
key: ${{ runner.os }}-node-${{ hashFiles('package-lock.json') }}
restore-keys: |
${{ runner.os }}-node-
# Instalar si no hay cache
- name: Install dependencies
if: steps.cache-npm.outputs.cache-hit != 'true'
run: npm ci
- run: npm run build
Estrategias de cache
| Objetivo | Ejemplo de key | Restore key |
|---|---|---|
| npm | ${{ runner.os }}-node-${{ hashFiles('**/package-lock.json') }} | ${{ runner.os }}-node- |
| pip | ${{ runner.os }}-pip-${{ hashFiles('**/requirements.txt') }} | ${{ runner.os }}-pip- |
| Gradle | ${{ runner.os }}-gradle-${{ hashFiles('**/*.gradle*') }} | ${{ runner.os }}-gradle- |
Step 8: Workflows reutilizables
Composite Action (Action compuesta)
.github/actions/setup-node-and-install/action.yml
name: 'Setup Node and Install'
description: 'Setup Node.js and install dependencies'
inputs:
node-version:
description: 'Node.js version'
required: false
default: '20'
runs:
using: 'composite'
steps:
- name: Setup Node.js
uses: actions/setup-node@v4
with:
node-version: ${{ inputs.node-version }}
cache: 'npm'
- name: Install dependencies
run: npm ci
shell: bash
Ejemplo de uso:
jobs:
test:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: ./.github/actions/setup-node-and-install
with:
node-version: '20'
- run: npm test
Reusable Workflow (Workflow reutilizable)
.github/workflows/reusable-test.yml
name: Reusable Test Workflow
on:
workflow_call:
inputs:
node-version:
required: false
type: string
default: '20'
secrets:
npm-token:
required: false
jobs:
test:
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 test
Lado que invoca:
name: CI
on: [push]
jobs:
call-test:
uses: ./.github/workflows/reusable-test.yml
with:
node-version: '20'
secrets:
npm-token: ${{ secrets.NPM_TOKEN }}
Step 9: Workflows de despliegue
Despliegue a Vercel
name: Deploy to Vercel
on:
push:
branches: [main]
jobs:
deploy:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Deploy to Vercel
uses: amondnet/vercel-action@v25
with:
vercel-token: ${{ secrets.VERCEL_TOKEN }}
vercel-org-id: ${{ secrets.VERCEL_ORG_ID }}
vercel-project-id: ${{ secrets.VERCEL_PROJECT_ID }}
vercel-args: '--prod'
Despliegue a AWS S3 + CloudFront
name: Deploy to AWS
on:
push:
branches: [main]
jobs:
deploy:
runs-on: ubuntu-latest
permissions:
id-token: write
contents: read
steps:
- uses: actions/checkout@v4
- name: Configure AWS credentials (OIDC)
uses: aws-actions/configure-aws-credentials@v4
with:
role-to-assume: ${{ secrets.AWS_ROLE_ARN }}
aws-region: ap-northeast-1
- name: Build
run: |
npm ci
npm run build
- name: Deploy to S3
run: aws s3 sync dist/ s3://${{ secrets.S3_BUCKET }}
- name: Invalidate CloudFront
run: |
aws cloudfront create-invalidation \
--distribution-id ${{ secrets.CF_DISTRIBUTION_ID }} \
--paths "/*"
Consejos de debug
Habilitar logs de debug
Configurar en Secrets del repositorio:
ACTIONS_STEP_DEBUG=trueACTIONS_RUNNER_DEBUG=true
Debug dentro del workflow
- name: Debug info
run: |
echo "Event: ${{ github.event_name }}"
echo "Ref: ${{ github.ref }}"
echo "SHA: ${{ github.sha }}"
echo "Actor: ${{ github.actor }}"
echo "Repository: ${{ github.repository }}"
env
- name: Debug context
run: echo '${{ toJSON(github) }}'
Test local (act)
# Instalar act
brew install act
# Ejecutar localmente
act push
# Ejecutar job especifico
act -j test
Referencia: nektos/act
Errores comunes y antipatrones
1. Hardcodear secrets
# Mal ejemplo
- run: curl -H "Authorization: Bearer abc123" https://api.example.com
# Buen ejemplo
- run: curl -H "Authorization: Bearer ${{ secrets.API_TOKEN }}" https://api.example.com
2. Configuracion inapropiada de cache key
# Mal ejemplo: Key fija, cache no se actualiza
key: my-cache
# Buen ejemplo: Incluir hash de archivos de dependencia
key: ${{ runner.os }}-node-${{ hashFiles('**/package-lock.json') }}
3. Loop infinito
# Mal ejemplo: Se triggerea por su propio commit
on:
push:
branches: [main]
jobs:
commit:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- run: |
echo "update" >> file.txt
git add .
git commit -m "Auto update"
git push
Resumen
Con GitHub Actions obtienes las siguientes ventajas:
- Verificacion automatica de calidad de codigo
- Prevencion de errores humanos con despliegue automatizado
- Workflow consistente compartido por todo el equipo
- Excelente experiencia de desarrollo integrada con GitHub
Comienza con una simple automatizacion de tests y expande gradualmente hasta el despliegue.
Enlaces de referencia
Documentacion oficial
- GitHub Actions Documentation - Referencia oficial
- Workflow syntax - Sintaxis de workflow
- GitHub Actions Marketplace - Actions reutilizables
Mejores practicas y articulos
- Martin Fowler - Continuous Integration - Origen de CI
- DORA Metrics - Metricas de rendimiento DevOps
- GitHub Actions Security Best Practices - Guia de seguridad
Herramientas y recursos
- act - Ejecutar GitHub Actions localmente
- actionlint - Analisis estatico de archivos workflow
- GitHub Actions Cheat Sheet - Cheat sheet oficial
Libros
- “Continuous Delivery” (Jez Humble, David Farley) - Libro de texto de CD/CD
- “The DevOps Handbook” - Guia completa de DevOps