Si tu proyecto no tiene integración continua, cada push es una apuesta: "en mi máquina funciona". Con GitHub Actions puedes ejecutar tu linter y tus tests automáticamente en cada push y en cada pull request, gratis en repositorios públicos. En este tutorial armamos un workflow para Node.js que prueba tu código en dos versiones de Node a la vez, y explico cada línea para que lo adaptes a cualquier lenguaje.

1. Dónde va el archivo

GitHub busca workflows en .github/workflows/ dentro de tu repositorio. Cualquier archivo .yml ahí se convierte en un workflow. Crea .github/workflows/node-ci.yml.

2. El workflow completo

name: Node.js CI on: push: branches: [main] pull_request: branches: [main] concurrency: group: ${{ github.workflow }}-${{ github.ref }} cancel-in-progress: true permissions: contents: read jobs: test: runs-on: ubuntu-latest timeout-minutes: 15 strategy: fail-fast: false matrix: node-version: [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 run lint - run: npm test 

3. Qué hace cada parte

on: define cuándo se ejecuta. Aquí, en cada push a main y en cada PR dirigido a main. Si tu rama principal se llama master, cámbialo.

concurrency: si haces tres push seguidos a un PR, no tiene sentido terminar las dos primeras ejecuciones. Esta sección agrupa las ejecuciones por workflow y rama, y cancela las viejas. Ahorras minutos y obtienes feedback más rápido.

permissions: por defecto, el token del workflow puede tener permisos de escritura. Un CI solo necesita leer el código, así que lo limitamos a contents: read. Si alguna dependencia maliciosa intenta usar el token, no podrá modificar tu repo.

strategy.matrix: el truco más útil. GitHub crea un job por cada valor de la matriz, en paralelo. Con [20, 22] pruebas en ambas versiones LTS sin duplicar código. fail-fast: false evita que, si falla Node 20, se cancele Node 22: quieres ver ambos resultados para saber si el problema depende de la versión.

timeout-minutes: el límite por defecto es de 6 horas. Un test colgado puede consumir tus minutos gratuitos. 15 minutos es un tope razonable para la mayoría de los proyectos.

cache: npm: setup-node guarda la caché de npm usando el hash de tu package-lock.json como clave. Si el lockfile no cambia, la instalación es mucho más rápida.

npm ci en lugar de npm install: npm ci instala exactamente lo que dice el lockfile y falla si no coincide con package.json. Es reproducible, que es justo lo que quieres en CI.

4. Siguiente paso: protege tu rama

Una vez que el workflow pase, ve a Settings > Branches (o Rules) y exige que los checks pasen antes de hacer merge. A partir de ahí, nadie (ni tú con prisa un viernes) podrá fusionar código que rompe los tests.

El mismo patrón sirve para Python (setup-python o setup-uv con matriz ["3.11", "3.12", "3.13"], ¡entre comillas!) o Go (setup-go con go-version-file: go.mod).

¿Te sirvió? Este ejemplo es parte del Pack GitHub Actions CI en Español: paga lo que quieras, desde 1 USD. Más recursos gratis en GitHub.