GitHub Actions: pipelines de CI que no demoran una eternidad
Por Equipo Hexadevs · 19 jul 2026 · 3 min de lectura
La CI lenta es una de las principales razones por las que un equipo deja de correr tests antes de pushear. Una pipeline de 12 minutos no es protección, es un impuesto al flow.
El workflow mínimo decente
# .github/workflows/ci.yml
name: CI
on:
push:
branches: [main]
pull_request:
jobs:
test:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: pnpm/action-setup@v4
with:
version: 9
- uses: actions/setup-node@v4
with:
node-version: 22
cache: "pnpm"
- run: pnpm install --frozen-lockfile
- run: pnpm lint
- run: pnpm test
- run: pnpm build
Ese ya es un workflow funcional: lint, tests, build. Pero podemos hacerlo mejor.
Cachear dependencias y artefactos
Lo más caro de una pipeline es install. Con cachearlo, bajás la instalación de casi dos minutos a cuestión de segundos:
- uses: pnpm/action-setup@v4
- uses: actions/setup-node@v4
with:
node-version: 22
cache: "pnpm"
Y para artefactos entre jobs:
- uses: actions/upload-artifact@v4
with:
name: dist
path: apps/web/dist
retention-days: 7
Matrices para correr en paralelo
Si tenés tests en Node 20 y Node 22, o querés validar Linux y macOS:
strategy:
matrix:
node: [20, 22]
os: [ubuntu-latest, macos-latest]
fail-fast: false
runs-on: ${{ matrix.os }}
Con fail-fast: false, una falla no cancela el resto de la matriz. Útil cuando querés ver todos los fallos de una sola vez.
El patrón que más ahorra: sharding de tests
Cuando tu suite crece, el cuello de botella deja de ser install y pasa a ser tests. La solución es sharding:
strategy:
matrix:
shard: [1, 2, 3, 4]
steps:
- run: pnpm test --shard=${{ matrix.shard }}/4
Cuatro jobs paralelos que cada uno corre un cuarto de los tests. Si tenés 8 minutos de suite, terminás en poco más de 2.
Cancelá runs inútiles: concurrency
Si pusheás 5 commits en una hora, la CI corre 5 veces, aunque las 4 anteriores ya no sirvan. Sin cuidado, tus runners procesan builds muertos en serie:
concurrency:
group: ${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: true
Cada push nuevo sobre una misma rama cancela el run anterior. En un equipo activo, estas 2 líneas ahorran más minutos de CI que cualquier otra optimización de este post.
Reusable workflows: escribí el pipeline una sola vez
Cuando tenés varias apps y cada workflow repite el mismo bloque de install + lint + test, el primer bug que corrijas en un archivo y no en el resto te va a convencer. Con workflow_call, un workflow se vuelve invocable por otros:
# .github/workflows/lint.yml
on:
workflow_call:
# .github/workflows/ci-web.yml
jobs:
lint:
uses: ./.github/workflows/lint.yml
test-web:
uses: ./.github/workflows/test.yml
with:
package: "@hexadevs/web"
Un solo archivo por paso genérico, y el workflow de cada app queda siendo una lista de cinco líneas. El patrón escala con el monorepo en lugar de multiplicar el copy/paste.
Triggers inteligentes
No todo necesita correr en cada PR:
on:
push:
branches: [main]
pull_request:
paths:
- "apps/web/**"
- "packages/ui/**"
- "package.json"
- "pnpm-lock.yaml"
Un cambio solo de docs no debe disparar CI de código.
Una pipeline profesional corre en menos de 5 minutos. Si la tuya tarda más, no es una mejora “para después”: es la barrera que separa a tu equipo de deployar con confianza.
La primera optimización que hagas (cache de pnpm) ya te devuelve la mayor parte del beneficio. Las demás son incrementales. Pero la sensación de hacer push y ver los checks en verde en menos de 3 minutos cambia cómo trabaja tu equipo.