Cambiar tema

Pipeline CI con GitHub Actions: automatiza build y tests desde cero

Easton editorial illustration: solo-founder business system console

El móvil vibra. Un compañero escribe: «Producción caída; el código que fusionaste ayer tiene un problema.»

La cabeza te zumba. ¿No habías pasado los tests en local? Revisando los logs descubres que en local usabas Node 20 y en el entorno de pruebas Node 18; un bug por comportamiento distinto de una API. En ese momento solo quieres gritar: si hubiera un pipeline CI que ejecutara tests antes del merge, esto no habría pasado.

Olvidar tests manuales lo hacen nueve de cada diez desarrolladores. El que no olvida, probablemente ya se quemó antes. GitHub Actions existe para eso: tras un push, build, tests y despliegue automáticos. Sin preocuparte; la máquina se encarga.

Este artículo te guía para montar un pipeline CI completo desde cero: plantilla de workflow lista para copiar, estrategia Matrix para tests en paralelo con varias versiones (puede recortar más de la mitad el tiempo de build) y lecciones de la trinchera. ¿Listo? Empecemos.

Capítulo 1: Primeros pasos con GitHub Actions

Qué es GitHub Actions

En pocas palabras, GitHub Actions es la plataforma de automatización integrada en GitHub. Haces push en local y en la nube ejecuta tests, build y despliegue — todo automático.

Antes, CI/CD implicaba montar un servidor Jenkins: configuración, mantenimiento y upgrades. Lo potente de GitHub Actions: sin servidores ni instalaciones; un YAML en el repo y listo. Además, 2000 minutos gratis al mes (ilimitado en repos públicos), más que suficiente para proyectos personales y equipos pequeños.

Frente a Jenkins o Travis CI, GitHub Actions destaca por integración profunda con el repo (estado del build en el PR), configuración simple sin Groovy y un ecosistema enorme en Marketplace con miles de Actions listas. También tiene límites: atado a GitHub, migrar a GitLab implica reescribir; pipelines enterprise muy complejos pueden encajar mejor en Jenkins. Para la mayoría de proyectos, GitHub Actions basta.

Conceptos clave de un vistazo

Al empezar, varios conceptos pueden marear. En lenguaje claro:

Workflow (flujo de trabajo): un archivo YAML que define todo el proceso automatizado. Por ejemplo: «cada push a main ejecuta tests». Va en .github/workflows/.

Job (trabajo): un grupo de pasos dentro del workflow. Varios jobs pueden correr en paralelo o con dependencias. Por ejemplo, primero el job «test» y luego «deploy».

Step (paso): acción concreta dentro de un job, en orden. Puede ser un comando (npm test) o una Action (actions/checkout@v4).

Runner (ejecutor): la VM que ejecuta el job. GitHub ofrece ubuntu-latest (Linux), windows-latest (Windows) y macos-latest (macOS). También puedes usar tu propio servidor; en la mayoría de casos, el oficial basta.

Analogía: el workflow es el guion, los jobs son escenas, los steps son acciones en cada escena y el runner es el actor.

Tu primer workflow CI

No te compliques; haz que corra. En la raíz del proyecto crea .github/workflows/ci.yml y pega esto:

name: CI Pipeline  # Nombre del workflow, visible en la página Actions

on:
  push:
    branches: [main]  # Se dispara al hacer push a main
  pull_request:
    branches: [main]  # Se dispara cuando el PR apunta a main

permissions:
  contents: read  # Mínimo privilegio: solo lectura

jobs:
  build:
    runs-on: ubuntu-latest  # Ubuntu más reciente
    timeout-minutes: 15     # Timeout para evitar bloqueos

    steps:
      - name: Checkout code
        uses: actions/checkout@v4  # Obtener código

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

      - name: Install dependencies
        run: npm ci          # Instalar dependencias; ci es más rápido y fiable que install

      - name: Run tests
        run: npm test        # Ejecutar tests

      - name: Build
        run: npm run build   # Build

¿Qué hace?

on define cuándo se dispara: push a main o PR hacia main. permissions declara permisos con mínimo privilegio (contents: read) para evitar modificaciones accidentales del repo. Lo central es el job build en Ubuntu: checkout, Node, dependencias, tests y build.

Haz commit, push a GitHub y abre Actions. Verás un círculo verde girando: el runner ejecuta tu workflow. Tras unos minutos, si todo son marcas verdes, tu primer pipeline CI funciona.

¿Aparece una X roja? Entra en los logs; cada paso está detallado. En el 90% de los casos falla la instalación de dependencias o los tests en sí, no la configuración CI.

Capítulo 2: Configuración central del pipeline CI

El workflow del capítulo 1 corre, pero aún no es óptimo. Aquí repasamos cuatro piezas: disparadores, permisos, variables de entorno y caché de dependencias. Son el esqueleto del pipeline; bien configuradas, el workflow es más seguro y eficiente.

Disparadores: cuándo ejecutar

Los disparadores deciden cuándo arranca el workflow. Los más usados: push y pull_request.

on:
  push:
    branches: [main, dev]    # Push a main o dev
    paths:
      - 'src/**'             # Solo si cambian archivos bajo src/
      - 'package.json'       # package.json también dispara (actualización de deps)
  pull_request:
    branches: [main]         # PR hacia main

El filtro paths es muy útil. Si tienes docs/, cambiar documentación no debería disparar CI. Con paths, solo cambios de código activan el build: ahorras recursos y tiempo.

Además de push y PR:

schedule: tareas programadas con cron. Por ejemplo, build diario a medianoche:

on:
  schedule:
    - cron: '0 0 * * *'  # Cada día a las 00:00 UTC (08:00 hora de Pekín)

En un proyecto lo uso para revisar dependencias: npm outdated cada día y aviso por correo.

workflow_dispatch: disparo manual. A veces quieres un build sin push (probar una config). En Actions aparece «Run workflow».

on:
  workflow_dispatch:  # Manual, sin config extra

Permisos: seguridad primero

GitHub Actions asigna por defecto un GITHUB_TOKEN con lectura/escritura en el repo, creación de PR e incluso push. Cómodo, pero arriesgado: un workflow comprometido puede dar permisos de escritura.

En 2021, el CI de un proyecto open source fue explotado con un PR falsificado. La lección: declara explícitamente el mínimo privilegio.

permissions:
  contents: read    # Solo lectura del repo
  pull-requests: write  # Si necesitas crear PR, decláralo aparte

Para CI puro (tests y build), contents: read basta. Para releases o comentarios en PR, añade solo lo necesario.

Truco: en ajustes del repo, cambia el permiso por defecto a «Read repository contents permission». Todos los workflows empiezan en solo lectura; lo que escriba se declara aparte. Una capa más de defensa.

Variables de entorno: gestión por capas

Hay tres niveles: workflow, job y step. Cuanto más bajo el nivel, menor el alcance, pero puede sobrescribir el superior.

env:
  NODE_ENV: production     # Nivel workflow; todos los jobs
  CI: true                 # Muchas herramientas cambian comportamiento si detectan CI

jobs:
  build:
    env:
      BUILD_TARGET: web    # Nivel job; solo en build

    steps:
      - name: Run custom script
        env:
          MY_VAR: hello    # Nivel step; solo en este paso
        run: echo $MY_VAR

¿Por qué capas? Ejemplo: jobs build y deploy. NODE_ENV lo comparten → workflow. BUILD_TARGET solo build → job. Un parámetro puntual de script → step.

¿Datos sensibles? Nunca en texto plano en el YAML. Secrets de GitHub: añades la clave en ajustes (p. ej. API_KEY) y referencias ${{ secrets.API_KEY }}. Se ocultan en logs.

steps:
  - name: Deploy to server
    env:
      SSH_KEY: ${{ secrets.SSH_KEY }}  # Desde Secrets
    run: |
      echo "$SSH_KEY" > private.key
      ssh -i private.key user@server 'deploy.sh'

Caché de dependencias: acelerar el build

Con cientos de paquetes npm, instalar desde cero en cada CI tarda mucho. En un proyecto mío, 3 minutos de install y 1 de tests: el 75% era instalación.

GitHub Actions cachea dependencias instaladas. Lo más simple: caché integrada en setup-node.

- uses: actions/setup-node@v4
  with:
    node-version: 20
    cache: 'npm'  # Caché automática de npm

Con cache: 'npm', la primera vez instala y guarda node_modules. Si package-lock.json no cambia, la siguiente toma caché: de 3 minutos a 10 segundos.

Con pnpm o yarn: cache: 'pnpm' o cache: 'yarn'.

Caché más fina con actions/cache:

- name: Cache dependencies
  uses: actions/cache@v4
  with:
    path: ~/.npm         # Directorio global de caché npm
    key: npm-${{ runner.os }}-${{ hashFiles('package-lock.json') }}
    restore-keys: |
      npm-${{ runner.os }}-

key identifica la caché. Aquí usa el hash de package-lock.json: si cambia el lock, caché nueva. restore-keys es respaldo: si no hay match exacto, restaura una parcial.

Con buena tasa de acierto, el build acelera mucho. En un proyecto: sin caché 4 min, con caché 1,5 min. Veinte builds al día: tiempo de sobra para escribir un post.

Capítulo 3: Estrategia Matrix — tests en paralelo

Es mi función favorita de GitHub Actions y el corazón de este artículo. Matrix convierte un job en varios en paralelo: distintas versiones, distintos SO. Un push lanza muchas tareas a la vez; el tiempo total suele ser menos de la mitad que en serie.

Qué es Matrix

Imagina probar Node 16, 18 y 20. Lo clásico: tres jobs duplicados o un job que cambia versión en serie — redundancia o mucho tiempo.

Matrix es como una tabla: eje horizontal versiones de Node, vertical sistemas operativos; cada celda es una tarea. GitHub genera todas las combinaciones y las ejecuta en paralelo.

strategy:
  matrix:
    node: [16, 18, 20]
    os: [ubuntu-latest, windows-latest]

Esto genera 6 jobs. El tiempo total depende del más lento, no de la suma.

Matriz de versiones: varios Node

En un proyecto usábamos Node 20 en desarrollo; un usuario reportó fallo en Node 18 por una API distinta. Con tests multi-versión antes, el bug no habría salido.

Matrix para varias versiones:

jobs:
  test:
    runs-on: ubuntu-latest
    strategy:
      fail-fast: false    # Si falla una versión, las demás siguen
      matrix:
        node-version: [16, 18, 20, 22]

    steps:
      - uses: actions/checkout@v4
      - uses: actions/setup-node@v4
        with:
          node-version: ${{ matrix.node-version }}
          cache: 'npm'
      - run: npm ci
      - run: npm test

Puntos clave:

  • matrix.node-version lista las versiones
  • ${{ matrix.node-version }} en steps; cada job recibe un valor distinto
  • fail-fast: false: un fallo no cancela el resto. Por defecto es true. En compatibilidad conviene desactivarlo para ver todos los resultados.

include y exclude: excluir combinaciones o añadir extras.

strategy:
  matrix:
    node-version: [16, 18, 20]
    os: [ubuntu-latest, windows-latest]
    exclude:
      - node-version: 16      # No probar Node 16 + Windows
        os: windows-latest
    include:
      - node-version: 20      # Node 20 extra en macOS
        os: macos-latest

exclude quita combinaciones; include añade otras.

Matriz de SO: tests multiplataforma

Si el proyecto corre en varios SO (p. ej. CLI), la matriz de OS ayuda.

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

    steps:
      - uses: actions/checkout@v4
      - uses: actions/setup-node@v4
        with:
          node-version: ${{ matrix.node-version }}
          cache: 'npm'
      - run: npm ci
      - run: npm test

Ten en cuenta:

Diferencias de plataforma: rutas \ vs /, comportamiento distinto de herramientas CLI. Tests multiplataforma detectan esto antes.

Coste: Linux 2000 min/mes gratis; Windows ×2; macOS ×10. macOS agota la cuota rápido.

Ahorro:

  • macOS solo si el proyecto lo necesita de verdad
  • macOS en workflow aparte con workflow_dispatch manual
  • repos públicos ilimitados: si puedes, abre el proyecto

Trucos de rendimiento

Matrix paraleliza, pero no sin límite. GitHub limita paralelismo por defecto. Puedes controlarlo:

strategy:
  max-parallel: 4  # Máximo 4 jobs simultáneos
  matrix:
    node-version: [16, 18, 20, 22]

Con muchas combinaciones (10+ jobs), max-parallel evita picos que consumen la cuota gratis.

Caché en Matrix: cada job tiene la suya; setup-node con cache lo gestiona. Con package-lock.json y node-version estables, la tasa de acierto es alta.

Menos pasos redundantes: lint no suele necesitar varias versiones:

jobs:
  lint:
    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 lint  # lint solo en Node 20

  test:
    needs: lint  # test tras lint OK
    runs-on: ubuntu-latest
    strategy:
      matrix:
        node-version: [16, 18, 20]
    steps:
      - uses: actions/checkout@v4
      - uses: actions/setup-node@v4
        with:
          node-version: ${{ matrix.node-version }}
          cache: 'npm'
      - run: npm ci
      - run: npm test

Lint una vez; test en tres versiones. Más eficiente.

Datos reales: sin Matrix, 3 versiones en serie: 12 minutos. Con Matrix en paralelo: 4 minutos (la más lenta). Ocho minutos menos; diez builds al día, en un mes tiempo para una película.

Capítulo 4: Experiencia práctica y resolución de problemas

Los tres capítulos anteriores explican la configuración. Aquí va una «lista de consulta rápida»: seguridad, rendimiento y troubleshooting. Cuando algo falle, empieza aquí.

Lista de seguridad

PrácticaDescripciónEjemplo
Declarar permissions explícitamenteNo confíes en permisos por defectopermissions: { contents: read }
Secrets para datos sensiblesAPI Key, SSH, etc. sin hardcode${{ secrets.API_KEY }}
Limitar ramas de disparoNo CI en todas las ramasbranches: [main]
Referenciar Actions por SHACommit concreto, no solo etiquetaactions/checkout@b4ffde65f46336ab88eb53be808477a39b6bc2b1
Definir timeoutEvita bloqueos que consumen cuotatimeout-minutes: 15

Muchos ignoran la versión de Actions. @v4 es cómodo, pero las etiquetas pueden cambiar — en teoría apuntar a código malicioso. SHA es más seguro aunque actualizar sea más manual. En producción, considera SHA.

Lista de rendimiento

TrucoEfectoConfiguración
Caché de dependenciasAhorra 50%+ en instalacióncache: 'npm'
npm ci en lugar de installMás rápido y predeciblerun: npm ci
timeout-minutesEvita bloqueostimeout-minutes: 15
Matrix en paralelo−60%+ tiempo totalstrategy.matrix
concurrency para cancelar builds duplicadosSolo el último en la ramaconcurrency.group: ${{ github.ref }}

concurrency es útil: cinco push seguidos en la misma rama disparan cinco builds; con concurrency se cancelan los cuatro primeros y corre solo el último.

concurrency:
  group: ${{ github.workflow }}-${{ github.ref }}
  cancel-in-progress: true  # Cancelar builds antiguos en curso

Tabla rápida de problemas frecuentes

Mensaje de errorCausaSolución
Permission deniedPermisos insuficientesRevisa permissions y añade lo necesario
Cache not foundClave de caché no coincideRevisa cache key y package-lock.json
npm ERR! networkTimeout de redMás timeout o mirror regional
Out of memoryNode sin memoriaNODE_OPTIONS=--max_old_space_size=4096
EACCES permission deniedPermisos de archivochmod +x script.sh al inicio
Error: Cannot find moduleDependencias incompletasConfirma que npm ci terminó bien

Escenarios habituales:

Timeout de red: a veces el runner tarda en npm registry. Mirror en .npmrc:

- name: Configure npm registry
  run: echo "registry=https://registry.npmmirror.com" > .npmrc

Memoria insuficiente: builds grandes pueden agotar heap de Node:

env:
  NODE_OPTIONS: --max_old_space_size=4096  # 4 GB para Node

Caché que no aplica: la primera ejecución es normal. Asegura package-lock.json (npm ci lo necesita) y que cache coincida con npm/pnpm/yarn.

Conclusión

En resumen: un YAML monta el pipeline CI; permisos con mínimo privilegio; variables por capas; caché recorta a la mitad la instalación; Matrix simplifica tests multi-versión en paralelo.

Copia la plantilla del capítulo 1, ajusta versión de Node y comandos del proyecto, y tendrás CI. Primero que corra; luego afina. Prueba Matrix aunque sea con dos versiones de Node: ver varias marcas verdes a la vez compensa.

Si tienes problemas con GitHub Actions, deja un comentario. Iré ampliando la tabla del capítulo 4 para que otros eviten los mismos tropiezos.

Montar un pipeline CI con GitHub Actions

Crea un pipeline CI completo desde cero para automatizar build y tests

⏱️ Estimated time: 30 min

  1. 1

    Step 1: Crear el directorio de workflows

    En la raíz del proyecto, crea el directorio `.github/workflows/` para guardar todos los archivos de configuración de workflows.
  2. 2

    Step 2: Escribir la configuración CI básica

    Crea el archivo `ci.yml` con condiciones de disparo (push/PR), permisos (mínimo privilegio) y pasos del job (checkout, setup-node, install, test, build).
  3. 3

    Step 3: Activar la caché de dependencias

    En el paso `setup-node`, añade el parámetro `cache: 'npm'` para cachear dependencias npm y acelerar builds posteriores.
  4. 4

    Step 4: Configurar tests multi-versión con Matrix

    Añade `strategy.matrix` con la lista de versiones de Node a probar (por ejemplo [16, 18, 20]) para ejecutar tests en paralelo.
  5. 5

    Step 5: Hacer commit y revisar el resultado

    Haz commit del archivo de configuración, push a GitHub y abre la pestaña Actions para ver el estado del build y los logs.

FAQ

¿Cuál es la cuota gratuita mensual de GitHub Actions?
En repos privados: 2000 minutos al mes (Linux Runner). En repos públicos: ilimitado. Windows consume el doble que Linux; macOS, 10 veces más.
¿En qué ramas debería dispararse el workflow CI?
Se recomienda solo en main/master y en PRs hacia main. Las ramas de desarrollo pueden omitir CI para ahorrar recursos. Usa el filtro `paths` para excluir cambios en documentación.
¿Por qué se recomienda npm ci en lugar de npm install?
npm ci es más rápido y fiable: instala estrictamente según package-lock.json, no modifica el lockfile y encaja bien en CI. npm install puede actualizar versiones y generar builds impredecibles.
¿Cuánto tiempo de build puede ahorrar la estrategia Matrix?
Datos reales: probar 3 versiones de Node en serie tarda 12 minutos; con Matrix en paralelo, unos 4 minutos (según la versión más lenta), más de un 60% menos.
¿Por qué a veces la caché no funciona?
La caché se basa en el hash de `package-lock.json`. Si cambia el lockfile, la caché caduca. La primera ejecución sin caché es normal. Asegúrate de que el parámetro `cache` coincida con tu gestor de paquetes (npm/pnpm/yarn).
¿Cómo usar información sensible (API Key, clave SSH) en CI?
Usa GitHub Secrets: añade la clave en la configuración del repositorio y referencia `${{ secrets.KEY_NAME }}` en el workflow. Los Secrets se ocultan automáticamente en los logs.

12 min de lectura · Publicado el: 6 abr 2026 · Actualizado el: 21 ago 2026

Comentarios

Inicia sesión con GitHub para dejar un comentario

Easton BlogEaston Blog