DEV Community

Asier Caballero
Asier Caballero

Posted on

Cancela ejecuciones anteriores si lanzas un nuevo commit en la misma PR

Todos hemos vivido ese sudor frío: haces git push a main un viernes a las seis de la tarde, cruzas los dedos y rezas para que el servidor de producción no empiece a escupir errores 500. Las oraciones no sustituyen a un pipeline de CI/CD bien configurado.

Si sigues conectándote por SSH a una VPS para hacer un git pull manual, o ejecutando la suite de tests en tu portátil antes de mergear "a ojo", necesitas automatizar esto ya. GitHub Actions se ha convertido en el estándar de facto para el nicho DevOps no solo porque está integrado donde vive tu código, sino porque la infraestructura que te da de gratis (o casi gratis) para runners es ridículamente buena.

En este GitHub Actions tutorial vamos a construir un pipeline completo, moderno y listo para producción, explicando qué pasa bajo el capó sin saltarnos los detalles técnicos que importan.


La anatomía de un Workflow (sin metáforas absurdas)

Para trabajar con GitHub Actions solo necesitas entender cómo piensa el motor de GitHub. Todo se define en archivos YAML dentro del directorio .github/workflows/ de tu repositorio.

El modelo mental se reduce a esto:

  1. Event (on): El detonante. Un push, una pull_request, un cron o un evento manual (workflow_dispatch).
  2. Job: Una secuencia de pasos que se ejecuta en una máquina virtual dedicada (Runner). Los jobs corren en paralelo por defecto, a menos que definas dependencias entre ellos.
  3. Step: Las tareas individuales dentro de un job. Un paso puede ejecutar un comando de terminal (run) o reutilizar un bloque de código empaquetado (uses).
  4. Action: Ese bloque reutilizable de código (normalmente un repositorio público) que te ahorra escribir 50 líneas de Bash para configurar Node, instalar Docker o autenticarte en AWS.

Un Pipeline Real: De Linting a despliegue automatizado

Vamos a montar un caso de uso real. Imagina una aplicación en Node.js (digamos v24) que queremos validar en cada Pull Request y desplegar como imagen Docker solo cuando el código llegue a la rama main.

Crea el archivo .github/workflows/ci-cd.yml:

name: CI/CD Pipeline

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

# Cancela ejecuciones anteriores si lanzas un nuevo commit en la misma PR
concurrency:
  group: ${{ github.workflow }}-${{ github.ref }}
  cancel-in-progress: true

permissions:
  contents: read
  packages: write

jobs:
  quality-check:
    name: Code Quality & Tests
    runs-on: ubuntu-latest
    steps:
      - name: Checkout del código
        uses: actions/checkout@v4

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

      - name: Instalar dependencias
        run: npm ci

      - name: Ejecutar Linter
        run: npm run lint

      - name: Correr tests unitarios
        run: npm test

  build-and-push:
    name: Build & Push Docker Image
    needs: quality-check
    if: github.event_name == 'push' && github.ref == 'refs/heads/main'
    runs-on: ubuntu-latest
    steps:
      - name: Checkout del código
        uses: actions/checkout@v4

      - name: Set up Docker Buildx
        uses: docker/setup-buildx-action@v3

      - name: Login en GitHub Container Registry
        uses: docker/login-action@v3
        with:
          registry: ghcr.io
          username: ${{ github.actor }}
          password: ${{ secrets.GITHUB_TOKEN }}

      - name: Extraer metadatos para Docker
        id: meta
        uses: docker/metadata-action@v5
        with:
          images: ghcr.io/${{ github.repository }}
          tags: |
            type=raw,value=latest
            type=sha,format=short

      - name: Build y Push de la imagen
        uses: docker/build-push-action@v6
        with:
          context: .
          push: true
          tags: ${{ steps.meta.outputs.tags }}
          labels: ${{ steps.meta.outputs.labels }}
          cache-from: type=gha
          cache-to: type=gha,mode=max
Enter fullscreen mode Exit fullscreen mode

Desgranando los detalles técnicos que marcan la diferencia

El código anterior no es el típico ejemplo trivial de "Hello World". Tiene decisiones de arquitectura que deberías aplicar desde el día uno:

1. Control de concurrencia

concurrency:
  group: ${{ github.workflow }}-${{ github.ref }}
  cancel-in-progress: true
Enter fullscreen mode Exit fullscreen mode

Si haces tres pushes seguidos a una Pull Request en menos de un minuto, no quieres consumir minutos de runners ejecutando tres veces la suite de tests. Esta directiva mata automáticamente los workflows anteriores del mismo branch y deja activo solo el último.

2. Principio de mínimo privilegio

permissions:
  contents: read
  packages: write
Enter fullscreen mode Exit fullscreen mode

Por defecto, el token implícito que genera GitHub (secrets.GITHUB_TOKEN) tenía históricamente demasiados permisos. Limitar explícitamente los ámbitos a nivel de workflow evita desastres de seguridad en caso de que una dependencia maliciosa intente inyectar código durante el build.

3. Caching agresivo de dependencias y Docker

Fíjate en esto:

  • En Node usas cache: 'npm' dentro de actions/setup-node. Esto guarda la carpeta ~/.npm entre ejecuciones usando el hash de tu package-lock.json como clave.
  • En la acción de Docker usas cache-from: type=gha y cache-to: type=gha,mode=max. Esto utiliza el sistema de cache nativo de GitHub Actions para las capas de Docker. La primera vez que compilas tu imagen tarda 2 minutos; las siguientes tardan 15 segundos porque aprovecha las capas sin cambios.

4. Encadenamiento de Jobs mediante needs

El job build-and-push tiene la directiva needs: quality-check. Esto le dice al motor de GitHub: "Ni se te ocurra empaquetar la imagen Docker si los tests fallaron en el job anterior". Además, mediante la condición if, nos aseguramos de que la imagen solo se publique cuando el evento sea un push a la rama main, ignorando las ejecuciones en PRs.


Secretos y Variables de Entorno: No metas la pata

Uno de los errores de novato más comunes es dejar credenciales expuestas en la configuración o subir archivos .env al repositorio.

Para manejar variables sensibles:

  1. Ve a tu repositorio en GitHub -> Settings -> Secrets and variables -> Actions.
  2. Define tus secretos ahí (por ejemplo, AWS_ACCESS_KEY_ID, DATABASE_URL, etc.).
  3. Consúmelos en tu YAML mediante el contexto de secretos:
- name: Desplegar a infraestructura
  env:
    DB_URI: ${{ secrets.DATABASE_URL }}
  run: |
    ./deploy-script.sh
Enter fullscreen mode Exit fullscreen mode

GitHub enmascara automáticamente el valor de estos secretos en los logs de salida. Si un comando imprime por error un secreto, verás *** en la consola.


Cómo depurar cuando todo rompe (porque va a romper)

El ciclo de desarrollo de un archivo YAML puede ser frustrante: commit -> push -> esperar al runner -> error de sintaxis -> repetir.

Para evitar saturar tu historial de Git con commits del tipo "fix typo in yaml", tienes dos opciones excelentes:

Usar act para probar en local

act es una herramienta en CLI que lee tu directorio .github/workflows/ y ejecuta los jobs localmente dentro de contenedores Docker.
Simplemente ejecutas en tu terminal:

act pull_request
Enter fullscreen mode Exit fullscreen mode

Y verás cómo se procesa tu pipeline exacta en tu máquina local sin haber subido nada a GitHub.

Habilitar logs de depuración

Si el problema ocurre directamente en los runners de GitHub, puedes crear un secreto en el repositorio llamado ACTIONS_STEP_DEBUG con el valor true. La próxima vez que corra el workflow, GitHub Actions escupirá un nivel de detalle extremo sobre qué variables de entorno se están exportando y qué comandos exactos está procesando la Shell bajo la superficie.


Siguientes pasos

Con esta base ya tienes un flujo que previene que rompas producción y elimina el trabajo manual de empaquetar artefactos.

A partir de aquí, el camino natural en DevOps pasa por explorar la estrategia de matrices (strategy: matrix) para probar tu código en múltiples versiones del entorno de ejecución simultáneamente, o implementar environments con aprobación manual antes de pasar de staging a producción. Pero no te compliques al principio: valida tu código, corre tus tests, construye tu imagen y automatiza el push. La magia del CI/CD es que la máquina trabaje para ti.

Top comments (0)