0%
Tutorial 10 de 10
100% completado

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
Push/PRGitHub ActionsTestsBuildDeployNotifications

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!