Fundamentos de GitHub Actions: workflow, job y step

1. Visión general

GitHub Actions es una herramienta de CI/CD integrada en GitHub que te permite automatizar build, test y despliegue sin necesidad de un servidor CI separado. Este artículo explica tres conceptos clave: workflow, job y step, con ejemplos concretos para un proyecto Spring Boot usando Maven.

Al terminar, podrás escribir un archivo workflow que ejecute tests automáticamente cada vez que hagas push de código, entender cómo los jobs se ejecutan en paralelo o secuencial, y saber cómo depurar cuando un workflow falla.

2. ¿Qué es un workflow?

Un workflow es un archivo YAML ubicado en la carpeta .github/workflows/ de tu repositorio. Cada workflow define cuándo ejecutarse (trigger) y qué hacer (jobs).

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

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

jobs:
  build:
    runs-on: ubuntu-latest
    steps:
      - name: Checkout code
        uses: actions/checkout@v4
      - name: Set up JDK 21
        uses: actions/setup-java@v4
        with:
          java-version: '21'
          distribution: 'temurin'
      - name: Build with Maven
        run: mvn -B package

Explicación:

  • name al inicio del archivo es el nombre mostrado en la pestaña Actions de GitHub.
  • on declara el trigger: aquí el workflow se ejecuta cuando hay push o pull_request en la rama main.
  • jobs.build es un único job llamado build, que se ejecuta en la máquina virtual ubuntu-latest.

Nota: El archivo debe estar en la ruta exacta .github/workflows/*.yml, si está en la carpeta incorrecta GitHub no reconocerá el workflow.

3. Job: unidad de ejecución independiente

Un workflow puede tener múltiples jobs. Cada job se ejecuta en un runner (máquina virtual) separado, y por defecto los jobs se ejecutan en paralelo a menos que declares needs.

jobs:
  test:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - run: mvn -B test

  deploy:
    runs-on: ubuntu-latest
    needs: test
    steps:
      - run: echo "Deploying application..."

Explicación:

  • test y deploy son dos jobs separados, cada job tiene su propio entorno runner limpio, sin compartir archivos del sistema entre sí.
  • needs: test obliga a deploy a esperar que test se complete exitosamente antes de ejecutarse — creando una relación secuencial.
  • Si no hay needs, GitHub Actions ejecutará test y deploy simultáneamente.

3.1. Compartir datos entre jobs

Debido a que cada job tiene su propio runner, para pasar archivos de un job a otro necesitas usar artifacts.

jobs:
  build:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - run: mvn -B package
      - uses: actions/upload-artifact@v4
        with:
          name: app-jar
          path: target/*.jar

  deploy:
    runs-on: ubuntu-latest
    needs: build
    steps:
      - uses: actions/download-artifact@v4
        with:
          name: app-jar

Explicación:

  • upload-artifact guarda el archivo .jar del job build en el almacenamiento temporal de GitHub.
  • download-artifact en el job deploy recupera ese archivo en el nuevo runner.
  • Esta es la única forma de transferir datos entre jobs (no puedes usar variables de entorno normales).

4. Step: la unidad de acción más pequeña

Un step es una acción específica dentro de un job, ejecutándose secuencialmente de arriba hacia abajo. Hay dos tipos principales de steps: usando uses (llamar a una action predefinida) o usando run (ejecutar un comando de shell).

steps:
  - name: Checkout code
    uses: actions/checkout@v4

  - name: Cache Maven dependencies
    uses: actions/cache@v4
    with:
      path: ~/.m2/repository
      key: ${{ runner.os }}-maven-${{ hashFiles('**/pom.xml') }}

  - name: Run tests
    run: mvn -B test

  - name: Print result
    run: echo "Test step finished with status ${{ job.status }}"

Explicación:

  • El step Checkout code usa la action predefinida actions/checkout para obtener el código fuente en el runner.
  • El step Cache Maven dependencies cachea el directorio ~/.m2/repository, ayudando a que los builds posteriores sean más rápidos al no tener que descargar nuevamente las dependencias.
  • La key del cache se basa en el hash del pom.xml — cuando el pom.xml cambia, el cache antiguo se descarta y se crea uno nuevo.
  • El último step usa run para ejecutar comandos de shell normales, pudiendo hacer referencia a contextos como ${{ job.status }}.

5. Variables de entorno y secrets

Frecuentemente necesitas pasar configuraciones sensibles (API keys, tokens) al workflow sin hard-codearlas en el archivo YAML.

jobs:
  deploy:
    runs-on: ubuntu-latest
    env:
      SPRING_PROFILES_ACTIVE: prod
    steps:
      - uses: actions/checkout@v4
      - name: Deploy to server
        env:
          DEPLOY_TOKEN: ${{ secrets.DEPLOY_TOKEN }}
        run: |
          echo "Deploying with profile $SPRING_PROFILES_ACTIVE"
          curl -H "Authorization: Bearer $DEPLOY_TOKEN" https://api.example.com/deploy

Explicación:

  • env a nivel de job se aplica a todos los steps en ese job (SPRING_PROFILES_ACTIVE).
  • env a nivel de step solo se aplica a ese step específico (DEPLOY_TOKEN).
  • secrets.DEPLOY_TOKEN se obtiene de Settings → Secrets and variables → Actions del repositorio, nunca mostrando el valor real en los logs.

Nota: Nunca imprimas directamente el valor de un secret en los logs usando echo $DEPLOY_TOKEN — aunque GitHub lo enmascara automáticamente, es mejor evitar imprimir secrets.

6. Matrix: ejecutar múltiples versiones simultáneamente

Cuando necesitas probar en múltiples versiones de Java u OS, matrix te ayuda a evitar escribir jobs repetidos.

jobs:
  test:
    runs-on: ubuntu-latest
    strategy:
      matrix:
        java: [17, 21]
    steps:
      - uses: actions/checkout@v4
      - uses: actions/setup-java@v4
        with:
          java-version: ${{ matrix.java }}
          distribution: 'temurin'
      - run: mvn -B test

Explicación:

  • strategy.matrix.java declara una lista de valores [17, 21].
  • GitHub Actions automáticamente crea 2 jobs en paralelo, cada uno ejecutándose con la versión de Java correspondiente.
  • Este enfoque ahorra trabajo en escribir YAML repetido, siendo ideal cuando necesitas asegurar que el código funciona en múltiples entornos.

7. Verificar el workflow con una prueba simple

Los workflows no se prueban directamente con JUnit, pero debes asegurar que el paso mvn test en el workflow realmente ejecute tu suite de tests. Aquí hay un test simple para OrderService que ejecutará el CI:

class OrderServiceTest {

    private final OrderService orderService = new OrderService();

    @Test
    void shouldCalculateTotalPriceCorrectly() {
        Order order = new Order("ORD-001", List.of(
                new OrderItem("Keyboard", 2, BigDecimal.valueOf(50)),
                new OrderItem("Mouse", 1, BigDecimal.valueOf(20))
        ));

        BigDecimal total = orderService.calculateTotal(order);

        assertThat(total).isEqualByComparingTo(BigDecimal.valueOf(120));
    }
}

Explicación:

  • Este test está en src/test/java, será ejecutado automáticamente por mvn -B test en el step del workflow.
  • Si el test falla, el step Run tests reporta un error (código de salida diferente de 0), el job se detiene y los jobs dependientes (needs) no se ejecutan.
  • Esta es la razón por la que CI es útil: el código erróneo se bloquea antes de fusionarse en main.

El resultado cuando se ejecuta en la UI de GitHub Actions (pestaña Actions):

✓ Checkout code
✓ Set up JDK 21
✓ Build with Maven
✓ Run tests (shouldCalculateTotalPriceCorrectly PASSED)

8. Errores comunes

  • Indentación YAML incorrecta: GitHub Actions usa YAML, una indentación incorrecta de un espacio causa que los jobs o steps no se reconozcan, generalmente reportando “Invalid workflow file”.
  • Olvidar runs-on: cada job requiere obligatoriamente runs-on, sin él el workflow fallará al analizar.
  • Usar variables de entorno de un job en otro: env y las variables de salida normalmente no se transfieren automáticamente entre jobs, debes usar outputs o artifact.
  • Cache key que no cambia cuando las dependencias cambian: si la key del cache es muy genérica (por ejemplo solo maven-cache), usarás accidentalmente un cache antiguo con dependencias desactualizadas.
  • Ejecutar workflows pesados en cada push: si no limitas paths o branches, cada commit pequeño (como editar README) desencadenará el pipeline completo de build + test, gastando minutos de ejecución de Actions (que tienen límite gratuito).

Nota: Si tu proyecto solo necesita un build simple y no tiene múltiples entornos, no necesitas matrix o artifact — agregar complejidad innecesaria hará el workflow difícil de mantener.

9. Conclusión

Un workflow es un archivo YAML que define todo el pipeline, un job es un bloque de ejecución independiente que corre en su propio runner, y un step es cada acción específica dentro de un job. Entender bien estos tres niveles te ayuda a organizar tu pipeline de CI/CD claramente: qué jobs se ejecutan en paralelo, qué jobs deben esperar a otros mediante needs, y cuándo usar artifact para compartir datos.

Cuando estés comenzando, mantén el workflow simple (un job, algunos steps básicos: checkout, setup Java, test). Conforme el proyecto crece, añade matrix, cache, y separa build/test/deploy en jobs independientes para facilitar la gestión y el debugging.

github-actionsci-cddevopsjavaautomation