Tutorial de GitHub Actions: Automatiza CI/CD para Tus Proyectos en 2026

Tutorial de GitHub Actions: Automatiza CI/CD para Tus Proyectos en 2026

Tutorial paso a paso de GitHub Actions. Aprende a automatizar pruebas, builds y despliegues con ejemplos prácticos para Node.js y Python.

17 de marzo de 20268 min de lectura

¿Qué es CI/CD y Por Qué Importa?

Imagina esto: terminas una funcionalidad, haces push al repositorio, y en los próximos cinco minutos ya sabes si tu código rompe los tests, si el estilo de código es correcto y si la app se despliega correctamente en staging. Sin tocar un solo comando manualmente.

Eso es CI/CD:

  • Integración Continua (CI): Cada push ejecuta tests y verificaciones automáticamente
  • Entrega/Despliegue Continuo (CD): El código validado se despliega automáticamente

El resultado: menos bugs en producción, feedback más rápido y desarrolladores que no dedican tiempo a tareas repetitivas.

GitHub Actions: La Plataforma CI/CD de GitHub

GitHub Actions es la solución CI/CD nativa de GitHub, disponible desde 2019 y hoy adoptada por millones de proyectos. Sus ventajas:

  • Integración total con GitHub: Disparadores nativos para push, PR, releases, issues
  • Marketplace con miles de acciones: Reutiliza lo que la comunidad ya construyó
  • Gratuito para repos públicos: Sin límites en proyectos open source
  • Runners en la nube: Ubuntu, Windows y macOS disponibles

Conceptos Clave

Workflow (workflow.yml)
├── Trigger (on: push, PR, schedule...)
└── Jobs (se ejecutan en paralelo por defecto)
    ├── Job: test
    │   └── Steps (se ejecutan en secuencia)
    │       ├── Step: Checkout código
    │       ├── Step: Instalar Node.js
    │       ├── Step: npm install
    │       └── Step: npm test
    └── Job: deploy (solo si test pasa)
        └── Steps
            └── Step: Deploy a Vercel
TérminoDescripción
WorkflowEl proceso completo de automatización (archivo YAML)
EventLo que dispara el workflow (push, PR, cron...)
JobUnidad de trabajo que corre en un runner
StepAcción individual dentro de un job
ActionMódulo reutilizable del Marketplace
RunnerLa máquina virtual donde se ejecuta el job
SecretVariable de entorno cifrada (para API keys)

Tu Primer Workflow: CI para Node.js

# .github/workflows/ci.yml

name: CI - Tests y Calidad de Código

# ¿Cuándo se ejecuta?
on:
  push:
    branches: [main, develop]
  pull_request:
    branches: [main]

jobs:
  test:
    name: Tests en Node.js ${{ matrix.node-version }}
    runs-on: ubuntu-latest

    # Ejecutar en múltiples versiones de Node.js
    strategy:
      matrix:
        node-version: [20.x, 22.x]
      fail-fast: false  # No cancela otras versiones si una falla

    steps:
      - name: 📥 Checkout del código
        uses: actions/checkout@v4

      - name: 🟢 Configurar Node.js ${{ matrix.node-version }}
        uses: actions/setup-node@v4
        with:
          node-version: ${{ matrix.node-version }}
          cache: 'npm'  # Cachear dependencias automáticamente

      - name: 📦 Instalar dependencias
        run: npm ci  # Más estricto que npm install, ideal para CI

      - name: 🔍 Verificar tipos (TypeScript)
        run: npm run typecheck

      - name: 🎨 Verificar formato (ESLint + Prettier)
        run: npm run lint

      - name: 🧪 Ejecutar tests con cobertura
        run: npm test -- --coverage --ci

      - name: 📊 Subir reporte de cobertura
        uses: codecov/codecov-action@v4
        if: matrix.node-version == '22.x'  # Solo para una versión
        with:
          token: ${{ secrets.CODECOV_TOKEN }}
          fail_ci_if_error: false

Workflow para Python

# .github/workflows/python-ci.yml

name: CI - Python

on:
  push:
    branches: [main]
  pull_request:
    branches: [main]

jobs:
  quality:
    name: Calidad y Tests Python ${{ matrix.python-version }}
    runs-on: ubuntu-latest

    strategy:
      matrix:
        python-version: ["3.11", "3.12", "3.13"]

    steps:
      - uses: actions/checkout@v4

      - name: Configurar Python ${{ matrix.python-version }}
        uses: actions/setup-python@v5
        with:
          python-version: ${{ matrix.python-version }}
          cache: 'pip'

      - name: Instalar dependencias
        run: |
          python -m pip install --upgrade pip
          pip install -r requirements.txt
          pip install -r requirements-dev.txt

      - name: Verificar formato con black
        run: black --check --diff .

      - name: Lint con ruff
        run: ruff check .

      - name: Verificar tipos con mypy
        run: mypy app/ --strict

      - name: Tests con pytest
        run: |
          pytest \
            --cov=app \
            --cov-report=xml \
            --cov-report=term-missing \
            -v \
            --tb=short

      - name: Subir cobertura
        uses: codecov/codecov-action@v4
        with:
          file: ./coverage.xml

Usando Secrets de Forma Segura

Los API keys, contraseñas y tokens nunca deben estar en el código. GitHub Secrets los cifra y los hace disponibles de forma segura.

Configurar Secrets

  1. Ve a tu repositorio → SettingsSecrets and variablesActions
  2. Haz clic en New repository secret
  3. Nombre: DATABASE_URL, Valor: postgresql://user:pass@host/db

Usar Secrets en el Workflow

steps:
  - name: Desplegar aplicación
    env:
      # Los secrets se referencian así:
      DATABASE_URL: ${{ secrets.DATABASE_URL }}
      API_KEY: ${{ secrets.ANTHROPIC_API_KEY }}
      AWS_ACCESS_KEY_ID: ${{ secrets.AWS_ACCESS_KEY_ID }}
      AWS_SECRET_ACCESS_KEY: ${{ secrets.AWS_SECRET_ACCESS_KEY }}
    run: |
      echo "Iniciando despliegue..."
      ./scripts/deploy.sh

Importante: GitHub enmascara los valores de los secrets en los logs (***), pero nunca los imprimas explícitamente con echo.

Pipeline Completo: CI + CD a Producción

# .github/workflows/full-pipeline.yml

name: Pipeline Completo

on:
  push:
    branches: [main]
  pull_request:
    branches: [main]

# Cancelar runs anteriores del mismo branch
concurrency:
  group: ${{ github.workflow }}-${{ github.ref }}
  cancel-in-progress: true

jobs:
  # ── 1. Tests ────────────────────────────────────
  test:
    name: Tests
    runs-on: ubuntu-latest
    timeout-minutes: 10

    services:
      postgres:
        image: postgres:17
        env:
          POSTGRES_PASSWORD: testpass
          POSTGRES_DB: test_db
        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: '22'
          cache: 'npm'
      - run: npm ci
      - run: npm run lint
      - run: npm test
        env:
          DATABASE_URL: postgresql://postgres:testpass@localhost:5432/test_db
          NODE_ENV: test

  # ── 2. Build de Docker Image ─────────────────────
  build:
    name: Build Docker Image
    runs-on: ubuntu-latest
    needs: [test]  # Solo si test pasa
    if: github.ref == 'refs/heads/main'  # Solo en main

    outputs:
      image-tag: ${{ steps.meta.outputs.version }}

    steps:
      - uses: actions/checkout@v4

      - name: Extraer metadata para Docker
        id: meta
        uses: docker/metadata-action@v5
        with:
          images: ghcr.io/${{ github.repository }}
          tags: |
            type=sha,prefix=,suffix=,format=short
            type=raw,value=latest,enable=true

      - name: Login a GitHub Container Registry
        uses: docker/login-action@v3
        with:
          registry: ghcr.io
          username: ${{ github.actor }}
          password: ${{ secrets.GITHUB_TOKEN }}

      - name: Build y Push
        uses: docker/build-push-action@v5
        with:
          context: .
          push: true
          tags: ${{ steps.meta.outputs.tags }}
          cache-from: type=gha
          cache-to: type=gha,mode=max

  # ── 3. Deploy a Staging ───────────────────────────
  deploy-staging:
    name: Deploy a Staging
    runs-on: ubuntu-latest
    needs: [build]
    environment: staging  # Requiere aprobación manual si se configura

    steps:
      - uses: actions/checkout@v4

      - name: Deploy a Staging
        run: |
          echo "Desplegando versión ${{ needs.build.outputs.image-tag }}"
          # Aquí va el comando de deploy real (kubectl, helm, etc.)

  # ── 4. Tests de Integración en Staging ───────────
  e2e-tests:
    name: Tests E2E en Staging
    runs-on: ubuntu-latest
    needs: [deploy-staging]

    steps:
      - uses: actions/checkout@v4
      - uses: actions/setup-node@v4
        with:
          node-version: '22'
          cache: 'npm'
      - run: npm ci
      - name: Ejecutar tests Playwright
        run: npx playwright test
        env:
          BASE_URL: https://staging.tuapp.com

  # ── 5. Deploy a Producción ────────────────────────
  deploy-production:
    name: Deploy a Producción
    runs-on: ubuntu-latest
    needs: [e2e-tests]
    environment: production  # Requiere aprobación manual

    steps:
      - name: Deploy a Producción
        run: echo "Desplegando en producción..."

Técnicas Avanzadas

Workflows Reutilizables

# .github/workflows/reusable-deploy.yml
on:
  workflow_call:
    inputs:
      environment:
        required: true
        type: string
      image-tag:
        required: true
        type: string
    secrets:
      DEPLOY_KEY:
        required: true

jobs:
  deploy:
    runs-on: ubuntu-latest
    steps:
      - name: Deploy ${{ inputs.environment }}
        env:
          DEPLOY_KEY: ${{ secrets.DEPLOY_KEY }}
        run: |
          echo "Desplegando ${{ inputs.image-tag }} en ${{ inputs.environment }}"
# Llamar al workflow reutilizable
jobs:
  deploy:
    uses: ./.github/workflows/reusable-deploy.yml
    with:
      environment: production
      image-tag: ${{ needs.build.outputs.tag }}
    secrets:
      DEPLOY_KEY: ${{ secrets.PRODUCTION_DEPLOY_KEY }}

Condiciones y Filtros

# Solo ejecutar si cambian archivos en ciertas rutas
on:
  push:
    paths:
      - 'src/**'
      - 'tests/**'
      - 'package.json'
    paths-ignore:
      - '**.md'
      - 'docs/**'

jobs:
  deploy-backend:
    # Solo si los cambios son en el backend
    if: contains(github.event.commits[0].modified, 'backend/')
    runs-on: ubuntu-latest
    steps:
      - run: echo "Solo se despliega el backend"

Notificaciones de Estado

  notify:
    runs-on: ubuntu-latest
    needs: [deploy-production]
    if: always()  # Ejecutar siempre, incluso si falla

    steps:
      - name: Notificar éxito en Slack
        if: needs.deploy-production.result == 'success'
        uses: slackapi/slack-github-action@v1
        with:
          payload: |
            {
              "text": "✅ Deploy exitoso en producción - ${{ github.sha }}"
            }
        env:
          SLACK_WEBHOOK_URL: ${{ secrets.SLACK_WEBHOOK }}

      - name: Notificar fallo
        if: needs.deploy-production.result == 'failure'
        uses: slackapi/slack-github-action@v1
        with:
          payload: |
            {
              "text": "❌ Deploy FALLÓ en producción - <${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}|Ver logs>"
            }
        env:
          SLACK_WEBHOOK_URL: ${{ secrets.SLACK_WEBHOOK }}

Checklist para un Pipeline Sólido

  • Tests unitarios en múltiples versiones del runtime
  • Verificación de tipos (TypeScript/mypy)
  • Linting y format checking
  • Tests de integración con servicios reales (BD, Redis)
  • Scan de vulnerabilidades de dependencias (npm audit, pip-audit)
  • Build de imagen Docker con cache
  • Deploy a staging antes de producción
  • Tests E2E en staging
  • Aprobación manual para producción
  • Notificaciones al equipo

Conclusión

GitHub Actions transforma tu repositorio en una factoría de software automatizada. Una vez que tienes el pipeline configurado, cada push activa un ciclo de verificación que garantiza la calidad del código antes de que llegue a producción.

El tiempo invertido en configurar este sistema se recupera en la primera semana: menos bugs escapados, menos deploys manuales fallidos, más confianza del equipo para hacer cambios frecuentes.

Empieza con el workflow más simple posible —solo tests— y ve añadiendo etapas según lo necesites. La automatización bien diseñada es incremental.

Publicaciones relacionadas