GitHub Actions es una plataforma de CI/CD (Integracion Continua/Entrega Continua) integrada en GitHub. Puedes automatizar tests, builds y despliegues usando push de codigo o pull requests como triggers. En este articulo explicamos en detalle como construir workflows practicos.
Lo que aprenderas en este articulo
- Conceptos basicos de GitHub Actions
- Sintaxis de archivos de workflow
- Automatizacion de tests para proyectos Node.js/Python
- Aceleracion mediante cache
- Matrix builds
- Despliegue a produccion
- Mejores practicas de seguridad
Conceptos basicos
Componentes de GitHub Actions
flowchart TB
subgraph Workflow["Workflow<br/>.github/workflows/ci.yml"]
subgraph TestJob["Job: test"]
Step1["Step 1: actions/checkout@v4"]
Step2["Step 2: actions/setup-node@v4"]
Step3["Step 3: npm install"]
Step4["Step 4: npm test"]
Step1 --> Step2 --> Step3 --> Step4
end
subgraph DeployJob["Job: deploy"]
DeploySteps["Deploy steps..."]
end
TestJob -->|depends on| DeployJob
end
| Termino | Descripcion |
|---|---|
| Workflow | Proceso de automatizacion completo definido en YAML |
| Job | Unidad de ejecucion dentro del workflow. Se ejecuta en el mismo runner |
| Step | Tareas individuales dentro del job. Action o comando shell |
| Action | Unidad de tarea reutilizable (actions/checkout, etc.) |
| Runner | Servidor que ejecuta el workflow |
Primer workflow
Ubicacion del archivo
project/
└── .github/
└── workflows/
├── ci.yml # Para tests y builds
├── deploy.yml # Para despliegues
└── release.yml # Para releases
Workflow CI basico
# .github/workflows/ci.yml
name: CI
on:
push:
branches: [main, develop]
pull_request:
branches: [main]
jobs:
test:
runs-on: ubuntu-latest
steps:
- name: Checkout repository
uses: actions/checkout@v4
- name: Setup Node.js
uses: actions/setup-node@v4
with:
node-version: '20'
cache: 'npm'
- name: Install dependencies
run: npm ci
- name: Run linter
run: npm run lint
- name: Run tests
run: npm test
- name: Build
run: npm run build
Detalles de triggers (on)
Varios triggers
on:
# Al hacer push
push:
branches:
- main
- 'release/**' # Pattern matching
paths:
- 'src/**' # Solo cuando cambian paths especificos
- '!src/**/*.md' # Excluir markdown
tags:
- 'v*' # Al crear tags
# Pull request
pull_request:
branches: [main]
types: [opened, synchronize, reopened]
# Ejecucion manual
workflow_dispatch:
inputs:
environment:
description: 'Deployment environment'
required: true
default: 'staging'
type: choice
options:
- staging
- production
# Ejecucion programada (UTC)
schedule:
- cron: '0 0 * * 1' # Cada lunes 00:00 UTC
# Llamada desde otro workflow
workflow_call:
inputs:
config-path:
required: true
type: string
Ejecucion condicional
jobs:
deploy:
# Solo ejecutar en push a main
if: github.ref == 'refs/heads/main' && github.event_name == 'push'
runs-on: ubuntu-latest
steps:
# ...
notify:
# Solo ejecutar si el job anterior fallo
if: failure()
needs: [test, deploy]
runs-on: ubuntu-latest
steps:
# ...
Variables de entorno y secrets
Configurar variables de entorno
# Nivel de workflow
env:
NODE_ENV: production
CI: true
jobs:
build:
runs-on: ubuntu-latest
# Nivel de job
env:
DATABASE_URL: postgres://localhost/test
steps:
- name: Build with environment
# Nivel de step
env:
API_KEY: ${{ secrets.API_KEY }}
run: |
echo "Node env: $NODE_ENV"
echo "Building..."
Uso de secrets
steps:
- name: Deploy to production
env:
AWS_ACCESS_KEY_ID: ${{ secrets.AWS_ACCESS_KEY_ID }}
AWS_SECRET_ACCESS_KEY: ${{ secrets.AWS_SECRET_ACCESS_KEY }}
run: |
aws s3 sync ./dist s3://my-bucket
# GITHUB_TOKEN esta disponible automaticamente
- name: Create release
uses: actions/create-release@v1
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
Environment Secrets
jobs:
deploy-prod:
runs-on: ubuntu-latest
environment:
name: production
url: https://myapp.com
steps:
# Acceso a variables y secrets del environment production
- name: Deploy
env:
DATABASE_URL: ${{ secrets.DATABASE_URL }}
run: ./deploy.sh
Estrategias de cache
Cache de npm
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: '20'
cache: 'npm' # Cache automatico
- run: npm ci
Cache personalizado
steps:
- name: Cache node modules
id: cache-npm
uses: actions/cache@v4
with:
path: ~/.npm
key: ${{ runner.os }}-npm-${{ hashFiles('**/package-lock.json') }}
restore-keys: |
${{ runner.os }}-npm-
- name: Install dependencies
if: steps.cache-npm.outputs.cache-hit != 'true'
run: npm ci
# Cache de artefactos de build
- name: Cache build output
uses: actions/cache@v4
with:
path: |
.next/cache
dist
key: ${{ runner.os }}-build-${{ github.sha }}
restore-keys: |
${{ runner.os }}-build-
Cache de dependencias (multiples lenguajes)
steps:
# Python
- uses: actions/setup-python@v5
with:
python-version: '3.12'
cache: 'pip'
# Go
- uses: actions/setup-go@v5
with:
go-version: '1.22'
cache: true
# Rust
- uses: actions/cache@v4
with:
path: |
~/.cargo/bin/
~/.cargo/registry/index/
~/.cargo/registry/cache/
target/
key: ${{ runner.os }}-cargo-${{ hashFiles('**/Cargo.lock') }}
Matrix builds
Tests en multiples versiones
jobs:
test:
runs-on: ubuntu-latest
strategy:
matrix:
node-version: [18, 20, 22]
include:
- node-version: 20
is-primary: true
fail-fast: false # Continuar aunque uno falle
steps:
- uses: actions/checkout@v4
- name: Use Node.js ${{ matrix.node-version }}
uses: actions/setup-node@v4
with:
node-version: ${{ matrix.node-version }}
- run: npm ci
- run: npm test
# Reporte de cobertura solo en version primaria
- name: Upload coverage
if: matrix.is-primary
uses: codecov/codecov-action@v4
Tests multiplataforma
jobs:
test:
strategy:
matrix:
os: [ubuntu-latest, macos-latest, windows-latest]
node-version: [18, 20]
exclude:
# Saltar combinacion Windows + Node 18
- os: windows-latest
node-version: 18
runs-on: ${{ matrix.os }}
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: ${{ matrix.node-version }}
- run: npm ci
- run: npm test
Dependencias entre jobs
Ejecucion secuencial
jobs:
lint:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- run: npm ci && npm run lint
test:
needs: lint # Ejecutar despues de que lint tenga exito
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- run: npm ci && npm test
build:
needs: [lint, test] # Depende de multiples jobs
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- run: npm ci && npm run build
Pasar artefactos entre jobs
jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- run: npm ci && npm run build
- name: Upload build artifacts
uses: actions/upload-artifact@v4
with:
name: dist
path: dist/
retention-days: 1
deploy:
needs: build
runs-on: ubuntu-latest
steps:
- name: Download build artifacts
uses: actions/download-artifact@v4
with:
name: dist
path: dist/
- name: Deploy
run: ./deploy.sh dist/
Ejemplos practicos de workflows
CI/CD completo para proyecto Node.js
# .github/workflows/ci-cd.yml
name: CI/CD
on:
push:
branches: [main]
pull_request:
branches: [main]
env:
NODE_VERSION: '20'
jobs:
# Verificacion de calidad de codigo
quality:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: ${{ env.NODE_VERSION }}
cache: 'npm'
- run: npm ci
- name: Type check
run: npm run type-check
- name: Lint
run: npm run lint
- name: Format check
run: npm run format:check
# Tests
test:
runs-on: ubuntu-latest
services:
postgres:
image: postgres:16
env:
POSTGRES_USER: test
POSTGRES_PASSWORD: test
POSTGRES_DB: test
ports:
- 5432:5432
options: >-
--health-cmd pg_isready
--health-interval 10s
--health-timeout 5s
--health-retries 5
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: ${{ env.NODE_VERSION }}
cache: 'npm'
- run: npm ci
- name: Run tests
env:
DATABASE_URL: postgres://test:test@localhost:5432/test
run: npm run test:coverage
- name: Upload coverage
uses: codecov/codecov-action@v4
with:
token: ${{ secrets.CODECOV_TOKEN }}
# Build
build:
needs: [quality, test]
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: ${{ env.NODE_VERSION }}
cache: 'npm'
- run: npm ci
- run: npm run build
- uses: actions/upload-artifact@v4
with:
name: build
path: dist/
# Despliegue a staging (al mergear PR)
deploy-staging:
needs: build
if: github.event_name == 'push' && github.ref == 'refs/heads/main'
runs-on: ubuntu-latest
environment:
name: staging
url: https://staging.myapp.com
steps:
- uses: actions/download-artifact@v4
with:
name: build
path: dist/
- name: Deploy to staging
env:
DEPLOY_KEY: ${{ secrets.STAGING_DEPLOY_KEY }}
run: |
echo "Deploying to staging..."
# Script de despliegue
# Despliegue a produccion (despues de aprobacion manual)
deploy-production:
needs: deploy-staging
runs-on: ubuntu-latest
environment:
name: production
url: https://myapp.com
steps:
- uses: actions/download-artifact@v4
with:
name: build
path: dist/
- name: Deploy to production
env:
DEPLOY_KEY: ${{ secrets.PRODUCTION_DEPLOY_KEY }}
run: |
echo "Deploying to production..."
# Script de despliegue
Build y push de imagen Docker
name: Docker Build
on:
push:
tags: ['v*']
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 Container Registry
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=semver,pattern={{version}}
type=semver,pattern={{major}}.{{minor}}
type=sha
- name: Build and push
uses: docker/build-push-action@v5
with:
context: .
push: true
tags: ${{ steps.meta.outputs.tags }}
labels: ${{ steps.meta.outputs.labels }}
cache-from: type=gha
cache-to: type=gha,mode=max
Workflows reutilizables
Definir workflow invocable
# .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 }}
cache: 'npm'
registry-url: 'https://registry.npmjs.org'
- run: npm ci
env:
NODE_AUTH_TOKEN: ${{ secrets.npm-token }}
- run: npm test
Invocar workflow reutilizable
# .github/workflows/ci.yml
name: CI
on: [push, pull_request]
jobs:
call-test:
uses: ./.github/workflows/reusable-test.yml
with:
node-version: '20'
secrets:
npm-token: ${{ secrets.NPM_TOKEN }}
Mejores practicas de seguridad
Escaneo de dependencias
name: Security
on:
push:
branches: [main]
schedule:
- cron: '0 0 * * 1' # Semanalmente
jobs:
dependency-scan:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Run npm audit
run: npm audit --audit-level=high
- name: Run Snyk scan
uses: snyk/actions/node@master
env:
SNYK_TOKEN: ${{ secrets.SNYK_TOKEN }}
Manejo seguro de secrets
steps:
# Mal ejemplo: Puede mostrar secret en logs
- name: Bad example
run: echo ${{ secrets.API_KEY }} # Puede aparecer en logs
# Buen ejemplo: Pasar como variable de entorno
- name: Good example
env:
API_KEY: ${{ secrets.API_KEY }}
run: ./deploy.sh # Usar $API_KEY dentro del script
Principio de minimo privilegio
# Establecer permisos minimos a nivel de workflow
permissions:
contents: read
packages: write
jobs:
build:
runs-on: ubuntu-latest
# Tambien se puede sobrescribir a nivel de job
permissions:
contents: read
steps:
# ...
Debug y solucion de problemas
Habilitar logs de debug
# Configurar en Secrets del repositorio
# ACTIONS_STEP_DEBUG: true
# ACTIONS_RUNNER_DEBUG: true
steps:
- name: Debug info
run: |
echo "GitHub Context:"
echo '${{ toJson(github) }}'
echo "Runner Context:"
echo '${{ toJson(runner) }}'
Re-ejecucion manual de jobs
on:
workflow_dispatch:
inputs:
debug_enabled:
description: 'Run with debug logging'
required: false
default: 'false'
jobs:
build:
runs-on: ubuntu-latest
steps:
- name: Setup tmate session
if: inputs.debug_enabled == 'true'
uses: mxschmitt/action-tmate@v3
Resumen
Resumimos los puntos clave de pipelines CI/CD con GitHub Actions.
Mejores practicas de diseno de workflows
- Separar jobs: Separar lint, test, build, deploy
- Aprovechar cache: Acelerar con cache de npm/pip, etc.
- Matrix builds: Tests paralelos en multiples entornos
- Separar environments: Separar claramente staging/production
Seguridad
- Principio de minimo privilegio: Establecer permissions necesarios
- Gestion de secrets: Aprovechar Environment Secrets
- Escaneo de dependencias: Verificacion periodica de vulnerabilidades
- Escaneo de codigo: Introducir CodeQL
Puntos para acelerar
- Configuracion apropiada de cache
- Maximizar ejecucion paralela
- Eliminar steps innecesarios
- Usar imagenes Docker slim
Puedes mejorar significativamente el proceso de desarrollo aprovechando GitHub Actions.