GitHub Actions y CI/CD
Configura workflows de integración y despliegue continuos directamente en tus repositorios de GitHub.
GitHub Actions y CI/CD
GitHub Actions revoluciona la automatización del desarrollo de software al integrar directamente CI/CD en tus repositorios. En este tutorial aprenderás a crear workflows potentes para automatizar pruebas, builds y despliegues.
Introducción a GitHub Actions
¿Qué es GitHub Actions?
GitHub Actions es una plataforma de automatización que permite:
- Ejecutar workflows en respuesta a eventos de Git
- Automatizar las pruebas y la validación
- Desplegar automáticamente tus aplicaciones
- Gestionar las releases y las notificaciones
Conceptos clave
Workflow: proceso automatizado definido en YAML Job: conjunto de pasos ejecutados en un runner Step: acción individual dentro de un job Action: componente reutilizable Runner: máquina virtual que ejecuta los jobs
Estructura de un workflow
Archivo básico
# .github/workflows/ci.yml
name: CI Pipeline
on:
push:
branches: [main, develop]
pull_request:
branches: [main]
jobs:
test:
runs-on: ubuntu-latest
steps:
- name: Checkout code
uses: actions/checkout@v4
- name: Setup Node.js
uses: actions/setup-node@v4
with:
node-version: '18'
- name: Install dependencies
run: npm ci
- name: Run tests
run: npm test
Disparadores (Triggers)
# Eventos de Git
on:
push:
branches: [main]
tags: ['v*']
pull_request:
branches: [main]
release:
types: [published]
# Eventos programados
on:
schedule:
- cron: '0 2 * * 1' # Todos los lunes a las 2:00
# Activación manual
on:
workflow_dispatch:
inputs:
environment:
description: 'Environment to deploy'
required: true
default: 'staging'
Workflows esenciales
CI básica para Node.js
# .github/workflows/node-ci.yml
name: Node.js CI
on:
push:
branches: [main]
pull_request:
branches: [main]
jobs:
test:
runs-on: ubuntu-latest
strategy:
matrix:
node-version: [16, 18, 20]
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: Run linter
run: npm run lint
- name: Run tests
run: npm test
- name: Run build
run: npm run build
- name: Upload coverage
uses: codecov/codecov-action@v3
with:
file: ./coverage/lcov.info
CI para Python
# .github/workflows/python-ci.yml
name: Python CI
on: [push, pull_request]
jobs:
test:
runs-on: ubuntu-latest
strategy:
matrix:
python-version: [3.8, 3.9, '3.10', 3.11]
steps:
- uses: actions/checkout@v4
- name: Set up Python ${{ matrix.python-version }}
uses: actions/setup-python@v4
with:
python-version: ${{ matrix.python-version }}
- name: Install dependencies
run: |
python -m pip install --upgrade pip
pip install -r requirements.txt
pip install -r requirements-dev.txt
- name: Lint with flake8
run: |
flake8 . --count --select=E9,F63,F7,F82 --show-source --statistics
flake8 . --count --exit-zero --max-complexity=10 --max-line-length=127
- name: Test with pytest
run: |
pytest --cov=./ --cov-report=xml
- name: Upload coverage
uses: codecov/codecov-action@v3
Despliegue automático
# .github/workflows/deploy.yml
name: Deploy to Production
on:
push:
branches: [main]
jobs:
deploy:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Setup Node.js
uses: actions/setup-node@v4
with:
node-version: '18'
- name: Install and build
run: |
npm ci
npm run build
- name: Deploy to Vercel
uses: amondnet/vercel-action@v25
with:
vercel-token: ${{ secrets.VERCEL_TOKEN }}
vercel-org-id: ${{ secrets.ORG_ID }}
vercel-project-id: ${{ secrets.PROJECT_ID }}
vercel-args: '--prod'
Actions avanzadas
Matriz de pruebas
jobs:
test:
runs-on: ${{ matrix.os }}
strategy:
matrix:
os: [ubuntu-latest, windows-latest, macos-latest]
node-version: [16, 18, 20]
include:
- os: ubuntu-latest
node-version: 20
coverage: true
exclude:
- os: windows-latest
node-version: 16
steps:
- uses: actions/checkout@v4
- name: Setup Node.js
uses: actions/setup-node@v4
with:
node-version: ${{ matrix.node-version }}
- name: Run tests
run: npm test
- name: Upload coverage
if: matrix.coverage
uses: codecov/codecov-action@v3
Condiciones y expresiones
jobs:
deploy:
if: github.ref == 'refs/heads/main' && github.event_name == 'push'
runs-on: ubuntu-latest
steps:
- name: Deploy to staging
if: contains(github.event.head_commit.message, '[staging]')
run: echo "Deploying to staging"
- name: Deploy to production
if: startsWith(github.ref, 'refs/tags/v')
run: echo "Deploying to production"
- name: Notify on failure
if: failure()
uses: 8398a7/action-slack@v3
with:
status: failure
webhook_url: ${{ secrets.SLACK_WEBHOOK }}
Artifacts y caché
jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Cache dependencies
uses: actions/cache@v3
with:
path: ~/.npm
key: ${{ runner.os }}-node-${{ hashFiles('**/package-lock.json') }}
restore-keys: |
${{ runner.os }}-node-
- name: Install and build
run: |
npm ci
npm run build
- name: Upload build artifacts
uses: actions/upload-artifact@v3
with:
name: build-files
path: dist/
deploy:
needs: build
runs-on: ubuntu-latest
steps:
- name: Download artifacts
uses: actions/download-artifact@v3
with:
name: build-files
path: dist/
- name: Deploy
run: echo "Deploying files from dist/"
Seguridad y secretos
Gestión de secretos
# Configuración de los secretos en GitHub
# Settings > Secrets and variables > Actions
jobs:
deploy:
runs-on: ubuntu-latest
steps:
- name: Deploy to AWS
env:
AWS_ACCESS_KEY_ID: ${{ secrets.AWS_ACCESS_KEY_ID }}
AWS_SECRET_ACCESS_KEY: ${{ secrets.AWS_SECRET_ACCESS_KEY }}
DATABASE_URL: ${{ secrets.DATABASE_URL }}
run: |
aws s3 sync ./dist s3://my-bucket
Entornos protegidos
jobs:
deploy-production:
runs-on: ubuntu-latest
environment: production
steps:
- name: Deploy to production
run: echo "Deploying to production environment"
Permisos de GITHUB_TOKEN
name: Secure Workflow
on: [push]
permissions:
contents: read
issues: write
pull-requests: write
jobs:
security-scan:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Run security scan
uses: github/super-linter@v4
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
Actions personalizadas
Action de JavaScript
// .github/actions/hello-world/index.js
const core = require('@actions/core');
const github = require('@actions/github');
try {
const nameToGreet = core.getInput('who-to-greet');
console.log(`Hello ${nameToGreet}!`);
const time = (new Date()).toTimeString();
core.setOutput("time", time);
const payload = JSON.stringify(github.context.payload, undefined, 2);
console.log(`The event payload: ${payload}`);
} catch (error) {
core.setFailed(error.message);
}
# .github/actions/hello-world/action.yml
name: 'Hello World'
description: 'Greet someone and record the time'
inputs:
who-to-greet:
description: 'Who to greet'
required: true
default: 'World'
outputs:
time:
description: 'The time we greeted you'
runs:
using: 'node20'
main: 'index.js'
Uso de la action
jobs:
hello_world_job:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Hello world action step
uses: ./.github/actions/hello-world
id: hello
with:
who-to-greet: 'GitHub Actions'
- name: Get the output time
run: echo "The time was ${{ steps.hello.outputs.time }}"
Workflows complejos
Pipeline multientorno
# .github/workflows/multi-env-deploy.yml
name: Multi-Environment Deploy
on:
push:
branches: [main, develop]
pull_request:
branches: [main]
jobs:
test:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Run tests
run: npm test
deploy-staging:
needs: test
if: github.ref == 'refs/heads/develop'
runs-on: ubuntu-latest
environment: staging
steps:
- uses: actions/checkout@v4
- name: Deploy to staging
run: echo "Deploying to staging"
deploy-production:
needs: test
if: github.ref == 'refs/heads/main'
runs-on: ubuntu-latest
environment: production
steps:
- uses: actions/checkout@v4
- name: Deploy to production
run: echo "Deploying to production"
notify:
needs: [deploy-staging, deploy-production]
if: always()
runs-on: ubuntu-latest
steps:
- name: Notify team
uses: 8398a7/action-slack@v3
with:
status: ${{ job.status }}
webhook_url: ${{ secrets.SLACK_WEBHOOK }}
Release automática
# .github/workflows/release.yml
name: Release
on:
push:
tags:
- 'v*'
jobs:
release:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
with:
fetch-depth: 0
- name: Setup Node.js
uses: actions/setup-node@v4
with:
node-version: '18'
- name: Install dependencies
run: npm ci
- name: Build
run: npm run build
- name: Generate changelog
id: changelog
uses: metcalfc/changelog-generator@v4.0.1
with:
myToken: ${{ secrets.GITHUB_TOKEN }}
- name: Create Release
uses: actions/create-release@v1
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
with:
tag_name: ${{ github.ref }}
release_name: Release ${{ github.ref }}
body: ${{ steps.changelog.outputs.changelog }}
draft: false
prerelease: false
- name: Upload Release Asset
uses: actions/upload-release-asset@v1
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
with:
upload_url: ${{ steps.create_release.outputs.upload_url }}
asset_path: ./dist/app.zip
asset_name: app.zip
asset_content_type: application/zip
Ejercicios prácticos
Ejercicio 1: CI básica
# .github/workflows/basic-ci.yml
name: Basic CI
on:
push:
branches: [main]
pull_request:
branches: [main]
jobs:
test:
runs-on: ubuntu-latest
steps:
- name: Checkout code
uses: actions/checkout@v4
- name: Setup Node.js
uses: actions/setup-node@v4
with:
node-version: '18'
- name: Install dependencies
run: |
echo '{"name": "test-app", "scripts": {"test": "echo \"Tests passed!\""}}' > package.json
npm install
- name: Run tests
run: npm test
- name: Success message
run: echo "✅ All tests passed!"
Ejercicio 2: Despliegue condicional
# .github/workflows/conditional-deploy.yml
name: Conditional Deploy
on:
push:
branches: [main, staging]
jobs:
deploy:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Deploy to staging
if: github.ref == 'refs/heads/staging'
run: |
echo "🚀 Deploying to staging environment"
echo "Environment: staging"
- name: Deploy to production
if: github.ref == 'refs/heads/main'
run: |
echo "🚀 Deploying to production environment"
echo "Environment: production"
- name: Notify deployment
run: |
echo "📧 Sending notification"
echo "Deployment completed for branch: ${{ github.ref_name }}"
Ejercicio 3: Workflow con artifacts
# .github/workflows/artifacts-demo.yml
name: Artifacts Demo
on: [push]
jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Create build artifacts
run: |
mkdir -p dist
echo "Built application v1.0.0" > dist/app.txt
echo "Build timestamp: $(date)" > dist/build-info.txt
- name: Upload artifacts
uses: actions/upload-artifact@v3
with:
name: build-artifacts
path: dist/
test:
needs: build
runs-on: ubuntu-latest
steps:
- name: Download artifacts
uses: actions/download-artifact@v3
with:
name: build-artifacts
path: ./downloaded/
- name: Test artifacts
run: |
echo "📦 Testing downloaded artifacts:"
ls -la ./downloaded/
cat ./downloaded/app.txt
cat ./downloaded/build-info.txt
Monitorización y depuración
Logs y depuración
jobs:
debug:
runs-on: ubuntu-latest
steps:
- name: Debug information
run: |
echo "🔍 Debug Information:"
echo "Runner OS: ${{ runner.os }}"
echo "GitHub Actor: ${{ github.actor }}"
echo "Repository: ${{ github.repository }}"
echo "Branch: ${{ github.ref_name }}"
echo "Commit SHA: ${{ github.sha }}"
- name: Enable debug logging
run: echo "::debug::This is a debug message"
- name: Set environment variable
run: echo "CUSTOM_VAR=hello-world" >> $GITHUB_ENV
- name: Use environment variable
run: echo "Custom variable: $CUSTOM_VAR"
Notificaciones
jobs:
notify:
runs-on: ubuntu-latest
if: always()
steps:
- name: Slack notification
uses: 8398a7/action-slack@v3
with:
status: ${{ job.status }}
channel: '#deployments'
webhook_url: ${{ secrets.SLACK_WEBHOOK }}
- name: Email notification
uses: dawidd6/action-send-mail@v3
with:
server_address: smtp.gmail.com
server_port: 465
username: ${{ secrets.MAIL_USERNAME }}
password: ${{ secrets.MAIL_PASSWORD }}
subject: "Build ${{ job.status }}"
body: "Build completed with status: ${{ job.status }}"
to: team@company.com
Buenas prácticas
Organización de los workflows
.github/
├── workflows/
│ ├── ci.yml # Pruebas y validación
│ ├── deploy-staging.yml # Despliegue en staging
│ ├── deploy-prod.yml # Despliegue en producción
│ ├── release.yml # Gestión de las releases
│ └── security.yml # Escaneos de seguridad
├── actions/
│ └── custom-action/ # Acciones personalizadas
└── ISSUE_TEMPLATE/ # Plantillas de issues
Optimización del rendimiento
jobs:
optimized:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
# Caché de dependencias
- name: Cache Node modules
uses: actions/cache@v3
with:
path: ~/.npm
key: ${{ runner.os }}-node-${{ hashFiles('**/package-lock.json') }}
# Paralelización
- name: Install dependencies
run: npm ci --prefer-offline --no-audit
# Evitar los pasos innecesarios
- name: Run tests
if: github.event_name == 'pull_request'
run: npm test
Seguridad
# Permisos mínimos
permissions:
contents: read
jobs:
secure:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
# Escaneo de seguridad
- name: Run Trivy vulnerability scanner
uses: aquasecurity/trivy-action@master
with:
scan-type: 'fs'
scan-ref: '.'
# Validación de los secretos
- name: Check for secrets
uses: trufflesecurity/trufflehog@main
with:
path: ./
Recursos adicionales
Acciones populares
- actions/checkout: Obtener el código
- actions/setup-node: Configurar Node.js
- actions/cache: Caché de dependencias
- docker/build-push-action: Build y push de Docker
- peaceiris/actions-gh-pages: Despliegue en GitHub Pages
Marketplace
Documentación
Conclusión
GitHub Actions convierte tu repositorio en una plataforma DevOps completa. Si dominas estos conceptos, podrás automatizar todo tu pipeline de desarrollo, desde la integración continua hasta el despliegue en producción.
¡Enhorabuena! Has terminado este curso completo sobre Git y GitHub. Ya cuentas con todas las competencias necesarias para gestionar eficazmente tus proyectos con Git, colaborar en GitHub y automatizar tus workflows de desarrollo.
Tiempo de lectura estimado: 40 minutos
Nivel: Avanzado
Etapa anterior: Git submódulos y subárboles
🎉 ¡Curso terminado! ¡Ya dominas Git y GitHub!