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:
nameal inicio del archivo es el nombre mostrado en la pestaña Actions de GitHub.ondeclara el trigger: aquí el workflow se ejecuta cuando haypushopull_requesten la ramamain.jobs.buildes un único job llamadobuild, que se ejecuta en la máquina virtualubuntu-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:
testydeployson dos jobs separados, cada job tiene su propio entorno runner limpio, sin compartir archivos del sistema entre sí.needs: testobliga adeploya esperar quetestse complete exitosamente antes de ejecutarse — creando una relación secuencial.- Si no hay
needs, GitHub Actions ejecutarátestydeploysimultá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-artifactguarda el archivo.jardel jobbuilden el almacenamiento temporal de GitHub.download-artifacten el jobdeployrecupera 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 codeusa la action predefinidaactions/checkoutpara obtener el código fuente en el runner. - El step
Cache Maven dependenciescachea el directorio~/.m2/repository, ayudando a que los builds posteriores sean más rápidos al no tener que descargar nuevamente las dependencias. - La
keydel cache se basa en el hash delpom.xml— cuando elpom.xmlcambia, el cache antiguo se descarta y se crea uno nuevo. - El último step usa
runpara 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:
enva nivel de job se aplica a todos los steps en ese job (SPRING_PROFILES_ACTIVE).enva nivel de step solo se aplica a ese step específico (DEPLOY_TOKEN).secrets.DEPLOY_TOKENse 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.javadeclara 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 pormvn -B testen el step del workflow. - Si el test falla, el step
Run testsreporta 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 obligatoriamenteruns-on, sin él el workflow fallará al analizar. - Usar variables de entorno de un job en otro:
envy las variables de salida normalmente no se transfieren automáticamente entre jobs, debes usaroutputsoartifact. - Cache key que no cambia cuando las dependencias cambian: si la
keydel cache es muy genérica (por ejemplo solomaven-cache), usarás accidentalmente un cache antiguo con dependencias desactualizadas. - Ejecutar workflows pesados en cada push: si no limitas
pathsobranches, 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
matrixoartifact— 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.