Guia completa de CI/CD con GitHub Actions - Construccion de pipelines de automatizacion

Intermedio | 75 min de lectura | 2025.12.02

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

  1. Conceptos basicos de GitHub Actions
  2. Sintaxis de archivos de workflow
  3. Automatizacion de tests para proyectos Node.js/Python
  4. Aceleracion mediante cache
  5. Matrix builds
  6. Despliegue a produccion
  7. 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
TerminoDescripcion
WorkflowProceso de automatizacion completo definido en YAML
JobUnidad de ejecucion dentro del workflow. Se ejecuta en el mismo runner
StepTareas individuales dentro del job. Action o comando shell
ActionUnidad de tarea reutilizable (actions/checkout, etc.)
RunnerServidor 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

  1. Separar jobs: Separar lint, test, build, deploy
  2. Aprovechar cache: Acelerar con cache de npm/pip, etc.
  3. Matrix builds: Tests paralelos en multiples entornos
  4. Separar environments: Separar claramente staging/production

Seguridad

  1. Principio de minimo privilegio: Establecer permissions necesarios
  2. Gestion de secrets: Aprovechar Environment Secrets
  3. Escaneo de dependencias: Verificacion periodica de vulnerabilidades
  4. Escaneo de codigo: Introducir CodeQL

Puntos para acelerar

  1. Configuracion apropiada de cache
  2. Maximizar ejecucion paralela
  3. Eliminar steps innecesarios
  4. Usar imagenes Docker slim

Puedes mejorar significativamente el proceso de desarrollo aprovechando GitHub Actions.

Enlaces de referencia

← Volver a la lista