Todo equipo de desarrollo moderno necesita una forma confiable de probar, construir y desplegar código automáticamente. GitHub Actions proporciona una plataforma CI/CD potente integrada directamente en GitHub, eliminando la necesidad de servicios externos como Jenkins o CircleCI. En esta guía, aprenderás cómo crear flujos de trabajo desde cero, ejecutar pruebas automatizadas, construir artefactos y desplegar aplicaciones — todo activado por un simple git push.

¿Qué Es CI/CD?

Integración Continua (CI) es la práctica de probar y validar automáticamente el código cada vez que un desarrollador hace push de cambios. En lugar de esperar hasta el día del lanzamiento para descubrir errores, CI los detecta inmediatamente.

Despliegue Continuo (CD) extiende CI desplegando automáticamente el código validado a entornos de staging o producción. Juntos, CI/CD crea un pipeline que lleva el código desde el commit hasta producción con mínima intervención manual.

Por qué importa: Los equipos que usan CI/CD lanzan más rápido, detectan errores antes y pasan menos tiempo en procesos de lanzamiento manuales. Un pipeline bien configurado puede reducir el tiempo de despliegue de horas a minutos.

Conceptos Fundamentales de GitHub Actions

Antes de escribir tu primer flujo de trabajo, entiende estos componentes básicos:

Workflows (Flujos de Trabajo)

Un workflow es un proceso automatizado definido en un archivo YAML dentro de .github/workflows/. Un repositorio puede tener múltiples workflows — uno para pruebas, otro para despliegue, y así sucesivamente.

Jobs (Trabajos)

Un workflow contiene uno o más jobs. Por defecto, los jobs se ejecutan en paralelo, pero puedes configurarlos para que se ejecuten secuencialmente usando dependencias.

Steps (Pasos)

Cada job contiene una serie de steps. Un step puede ejecutar un comando de shell o usar una action preconstruida del GitHub Marketplace.

Runners (Ejecutores)

Un runner es la máquina virtual que ejecuta tus jobs. GitHub proporciona runners alojados con Ubuntu, Windows y macOS. También puedes configurar runners auto-alojados para necesidades especializadas.

Actions (Acciones)

Las actions son unidades de código reutilizables. El GitHub Marketplace ofrece miles de actions creadas por la comunidad para tareas comunes como hacer checkout del código, configurar entornos de ejecución de lenguajes y desplegar en proveedores cloud.

Crear Tu Primer Flujo de Trabajo

Crea un archivo en .github/workflows/ci.yml en tu repositorio:

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: '20'

      - name: Install dependencies
        run: npm ci

      - name: Run linter
        run: npm run lint

      - name: Run tests
        run: npm test

Este flujo de trabajo hace lo siguiente:

  1. Se activa en pushes a main o develop, y en pull requests dirigidos a main
  2. Se ejecuta en el runner Ubuntu más reciente
  3. Hace checkout del código de tu repositorio
  4. Configura Node.js versión 20
  5. Instala dependencias usando npm ci (instalación limpia, más rápida y confiable que npm install)
  6. Ejecuta el linter y la suite de pruebas

Haz push de este archivo a tu repositorio y navega a la pestaña Actions para verlo ejecutarse.

Disparadores de Flujo de Trabajo

GitHub Actions soporta muchos eventos disparadores. Estos son los más comunes:

on:
  # Disparar en push a ramas específicas
  push:
    branches: [main]
    paths:
      - 'src/**'
      - 'package.json'

  # Disparar en pull requests
  pull_request:
    branches: [main]
    types: [opened, synchronize, reopened]

  # Ejecuciones programadas (sintaxis cron)
  schedule:
    - cron: '0 6 * * 1'  # Cada lunes a las 6 AM UTC

  # Disparador manual desde la interfaz de GitHub
  workflow_dispatch:
    inputs:
      environment:
        description: 'Target environment'
        required: true
        default: 'staging'
        type: choice
        options:
          - staging
          - production

Consejo: Usa filtros paths para evitar ejecutar flujos de trabajo costosos cuando solo cambian archivos de documentación. Esto ahorra minutos de runner y acelera tu ciclo de retroalimentación.

Usar Actions Preconstruidas

La organización actions/ en GitHub proporciona actions esenciales:

steps:
  # Hacer checkout de tu código
  - uses: actions/checkout@v4

  # Configurar varios entornos de ejecución de lenguajes
  - uses: actions/setup-node@v4
    with:
      node-version: '20'

  - uses: actions/setup-python@v5
    with:
      python-version: '3.12'

  - uses: actions/setup-go@v5
    with:
      go-version: '1.22'

  # Subir artefactos de construcción
  - uses: actions/upload-artifact@v4
    with:
      name: build-output
      path: dist/

  # Descargar artefactos de otro job
  - uses: actions/download-artifact@v4
    with:
      name: build-output

Siempre fija las actions a una versión mayor específica (por ejemplo, @v4) para evitar cambios incompatibles inesperados.

Variables de Entorno y Secretos

Variables de Entorno

Define variables a nivel de workflow, job o step:

env:
  NODE_ENV: production
  API_URL: https://api.knowledgexchange.xyz

jobs:
  build:
    runs-on: ubuntu-latest
    env:
      DATABASE_URL: postgres://localhost:5432/testdb
    steps:
      - name: Print environment
        run: echo "Building for $NODE_ENV"
        env:
          BUILD_NUMBER: ${{ github.run_number }}

Secretos

Nunca codifiques valores sensibles directamente. Almacénalos en Settings > Secrets and variables > Actions, luego referencíalos:

steps:
  - name: Deploy to production
    run: ./deploy.sh
    env:
      API_KEY: ${{ secrets.API_KEY }}
      DEPLOY_TOKEN: ${{ secrets.DEPLOY_TOKEN }}

Advertencia: Los secretos se enmascaran en los logs pero aún pueden ser expuestos accidentalmente. Nunca imprimas secretos directamente ni los escribas en archivos que se suban como artefactos.

Builds con Matrix

Prueba simultáneamente en múltiples versiones, sistemas operativos o configuraciones:

jobs:
  test:
    runs-on: ${{ matrix.os }}
    strategy:
      matrix:
        os: [ubuntu-latest, windows-latest, macos-latest]
        node-version: [18, 20, 22]
      fail-fast: false

    steps:
      - uses: actions/checkout@v4

      - name: Setup Node.js ${{ matrix.node-version }}
        uses: actions/setup-node@v4
        with:
          node-version: ${{ matrix.node-version }}

      - run: npm ci
      - run: npm test

Esto crea 9 jobs en paralelo (3 SO x 3 versiones de Node). Establecer fail-fast: false asegura que todas las combinaciones se ejecuten incluso si una falla, dándote una imagen completa de la compatibilidad.

Excluir e Incluir Combinaciones

strategy:
  matrix:
    os: [ubuntu-latest, windows-latest]
    node-version: [18, 20]
    exclude:
      - os: windows-latest
        node-version: 18
    include:
      - os: ubuntu-latest
        node-version: 22
        experimental: true

Caché de Dependencias

Acelera los flujos de trabajo almacenando en caché las dependencias descargadas:

steps:
  - uses: actions/checkout@v4

  - name: Setup Node.js
    uses: actions/setup-node@v4
    with:
      node-version: '20'
      cache: 'npm'  # Soporte de caché integrado

  - run: npm ci

Para más control, usa la action de caché directamente:

steps:
  - name: Cache pip packages
    uses: actions/cache@v4
    with:
      path: ~/.cache/pip
      key: ${{ runner.os }}-pip-${{ hashFiles('**/requirements.txt') }}
      restore-keys: |
        ${{ runner.os }}-pip-

  - name: Install dependencies
    run: pip install -r requirements.txt

La clave de caché se basa en el hash de tu archivo lock, por lo que el caché se invalida automáticamente cuando las dependencias cambian.

Construir y Subir Artefactos

Construye tu aplicación y guarda la salida para jobs posteriores o descarga:

jobs:
  build:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4

      - name: Setup Node.js
        uses: actions/setup-node@v4
        with:
          node-version: '20'
          cache: 'npm'

      - run: npm ci
      - run: npm run build

      - name: Upload build artifact
        uses: actions/upload-artifact@v4
        with:
          name: production-build
          path: dist/
          retention-days: 7

  deploy:
    needs: build
    runs-on: ubuntu-latest
    steps:
      - name: Download build artifact
        uses: actions/download-artifact@v4
        with:
          name: production-build
          path: dist/

      - name: Deploy
        run: echo "Deploying from dist/ directory"

La palabra clave needs: build asegura que el job de despliegue espere a que el job de construcción se complete exitosamente.

Ejemplo Práctico: Aplicación Node.js

Aquí tienes un pipeline CI/CD completo para una aplicación Node.js:

name: Node.js CI/CD

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

jobs:
  lint-and-test:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4

      - uses: actions/setup-node@v4
        with:
          node-version: '20'
          cache: 'npm'

      - run: npm ci

      - name: Run ESLint
        run: npm run lint

      - name: Run unit tests
        run: npm test -- --coverage

      - name: Upload coverage report
        if: github.event_name == 'pull_request'
        uses: actions/upload-artifact@v4
        with:
          name: coverage-report
          path: coverage/

  build:
    needs: lint-and-test
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4

      - uses: actions/setup-node@v4
        with:
          node-version: '20'
          cache: 'npm'

      - run: npm ci
      - run: npm run build

      - uses: actions/upload-artifact@v4
        with:
          name: build-output
          path: dist/

  deploy:
    needs: build
    if: github.ref == 'refs/heads/main' && github.event_name == 'push'
    runs-on: ubuntu-latest
    environment: production
    steps:
      - uses: actions/download-artifact@v4
        with:
          name: build-output
          path: dist/

      - name: Deploy to Cloudflare Pages
        uses: cloudflare/wrangler-action@v3
        with:
          apiToken: ${{ secrets.CLOUDFLARE_API_TOKEN }}
          accountId: ${{ secrets.CLOUDFLARE_ACCOUNT_ID }}
          command: pages deploy dist/ --project-name=my-app

Ejemplo Práctico: Aplicación Python

name: Python CI

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

jobs:
  test:
    runs-on: ubuntu-latest
    strategy:
      matrix:
        python-version: ['3.11', '3.12']

    services:
      postgres:
        image: postgres:16
        env:
          POSTGRES_PASSWORD: testpassword
          POSTGRES_DB: testdb
        options: >-
          --health-cmd pg_isready
          --health-interval 10s
          --health-timeout 5s
          --health-retries 5
        ports:
          - 5432:5432

    steps:
      - uses: actions/checkout@v4

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

      - name: Install dependencies
        run: |
          python -m pip install --upgrade pip
          pip install -r requirements.txt
          pip install -r requirements-dev.txt

      - name: Run flake8 linting
        run: flake8 src/ --count --show-source --statistics

      - name: Run pytest
        run: pytest --cov=src --cov-report=xml
        env:
          DATABASE_URL: postgres://postgres:testpassword@localhost:5432/testdb

      - name: Upload coverage to Codecov
        uses: codecov/codecov-action@v4
        with:
          file: ./coverage.xml
          token: ${{ secrets.CODECOV_TOKEN }}

Observa cómo este flujo de trabajo usa contenedores de servicio para levantar una base de datos PostgreSQL para pruebas de integración. GitHub Actions gestiona el ciclo de vida del contenedor automáticamente.

Desplegar un Sitio Estático en Vercel

name: Deploy to Vercel

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: '20'
          cache: 'npm'

      - run: npm ci
      - run: npm run build

      - name: Deploy to Vercel
        uses: amondnet/vercel-action@v25
        with:
          vercel-token: ${{ secrets.VERCEL_TOKEN }}
          vercel-org-id: ${{ secrets.VERCEL_ORG_ID }}
          vercel-project-id: ${{ secrets.VERCEL_PROJECT_ID }}
          vercel-args: '--prod'
          working-directory: ./

Flujos de Trabajo Reutilizables

Evita duplicar lógica de flujos de trabajo entre repositorios creando flujos de trabajo reutilizables:

# .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'
      - run: npm ci
        env:
          NPM_TOKEN: ${{ secrets.npm-token }}
      - run: npm test

Llámalo desde otro flujo de trabajo:

# .github/workflows/ci.yml
name: CI

on:
  push:
    branches: [main]

jobs:
  test:
    uses: ./.github/workflows/reusable-test.yml
    with:
      node-version: '20'
    secrets:
      npm-token: ${{ secrets.NPM_TOKEN }}

Ejecución Condicional y Salidas de Jobs

Controla cuándo se ejecutan steps y jobs usando condicionales:

jobs:
  check:
    runs-on: ubuntu-latest
    outputs:
      should-deploy: ${{ steps.changes.outputs.deploy }}
    steps:
      - uses: actions/checkout@v4

      - name: Check for deployment-relevant changes
        id: changes
        run: |
          if git diff --name-only HEAD~1 | grep -qE '^(src/|package\.json)'; then
            echo "deploy=true" >> "$GITHUB_OUTPUT"
          else
            echo "deploy=false" >> "$GITHUB_OUTPUT"
          fi

  deploy:
    needs: check
    if: needs.check.outputs.should-deploy == 'true'
    runs-on: ubuntu-latest
    steps:
      - run: echo "Deploying application..."

Mejores Prácticas

  1. Fija versiones de actions — Usa @v4 en lugar de @main para evitar roturas inesperadas
  2. Usa npm ci en lugar de npm install — Es más rápido y asegura builds reproducibles
  3. Habilita el caché — Almacena dependencias en caché para reducir drásticamente los tiempos de ejecución del flujo de trabajo
  4. Establece fail-fast: false en builds con matrix — Ve todos los fallos, no solo el primero
  5. Usa environments — Define entornos production y staging con puertas de aprobación
  6. Mantén los secretos al mínimo — Solo otorga los permisos que tu flujo de trabajo realmente necesita
  7. Añade insignias de estado — Muestra el estado del build en tu README:
![CI](https://github.com/username/repo/actions/workflows/ci.yml/badge.svg)
  1. Usa concurrency — Cancela ejecuciones de flujo de trabajo redundantes cuando se hacen push de nuevos commits:
concurrency:
  group: ${{ github.workflow }}-${{ github.ref }}
  cancel-in-progress: true

Depurar Flujos de Trabajo Fallidos

Cuando un flujo de trabajo falla, usa estas técnicas:

  • Revisa los logs en la pestaña Actions para el mensaje de error exacto
  • Añade logging de depuración estableciendo el secreto ACTIONS_STEP_DEBUG a true
  • Usa act para ejecutar flujos de trabajo localmente: act -j test (requiere Docker)
  • Conéctate por SSH al runner para depuración interactiva usando mxschmitt/action-tmate
- name: Debug with tmate
  if: failure()
  uses: mxschmitt/action-tmate@v3
  timeout-minutes: 15

Resumen

GitHub Actions proporciona todo lo que necesitas para construir un pipeline CI/CD robusto sin salir de GitHub. Comienza con un simple flujo de trabajo de pruebas, luego añade gradualmente pasos de construcción, destinos de despliegue y pruebas con matrix. La clave es automatizar las tareas repetitivas que ralentizan a tu equipo — deja que las máquinas se encarguen de las pruebas y el despliegue para que los desarrolladores puedan enfocarse en escribir código.

Para más sobre mejores prácticas de programación y flujos de trabajo de desarrollo, explora nuestros artículos de Programación. Si estás desplegando en infraestructura cloud, consulta nuestras guías sobre hosting cloud y configuración de servidores.