1. Primer workflow
Secciones
Ajustes de lectura
Voz natural grabada para este curso, la misma en todos los navegadores.
Ninguna voz para este idioma en este navegador.
Más voces
Código y términos técnicos en inglés
Desactivado: la voz de la página lo lee todo, sin pausa entre dos voces.
Ninguna voz para este idioma en este navegador.
Más voces
Más voces: Microsoft Edge ofrece voces naturales en línea, y Windows (Configuración › Hora e idioma › Voz), macOS y Android instalan otras.
El vocabulario, en correspondencia
Sección titulada «El vocabulario, en correspondencia»Un workflow es un archivo YAML en .github/workflows/. Un evento (un push, un pull request, un clic en Run workflow) inicia una ejecución (run) de ese workflow. Una ejecución contiene jobs; cada job recibe una máquina virtual nueva, el runner, y ejecuta sus steps en orden. Un step ejecuta un script de shell (run:) o llama a una action (uses:), un fragmento de código reutilizable publicado en un repositorio.
El mismo vocabulario en imagen: un evento inicia una ejecución, y cada job de la ejecución recibe su propio runner.
flowchart LR event["evento: un push, un pull request, Run workflow"] run["ejecución del workflow"] subgraph r1["runner: una máquina virtual nueva"] job1["job"] --> s1["step run: un script de shell"] --> s2["step uses: una action"] end subgraph r2["runner: otra máquina virtual nueva"] job2["job"] --> s3["sus propios steps, en orden"] end event --> run run --> job1 run --> job2| GitHub Actions | Azure Pipelines | Jenkins (declarativo) |
|---|---|---|
workflow (.github/workflows/*.yml) |
pipeline (azure-pipelines.yml) |
Jenkinsfile |
evento (on:) |
trigger:, pr:, schedules: |
triggers { } |
| job | job (los stages son opcionales) | stage |
step: run: |
script: / pwsh: |
sh / bat |
step: uses: (action) |
tarea (DotNetCoreCLI@2) |
step de plugin |
runner (runs-on: ubuntu-latest) |
agente (pool: vmImage: ubuntu-latest) |
agent { label '…' } |
Dos consecuencias de «cada job recibe una máquina nueva» sorprenden a quien viene de Jenkins:
- al empezar un job no hay nada en el disco, ni siquiera tu código: tienes que hacer el checkout explícitamente;
- los archivos no pasan de un job al siguiente: los jobs comparten datos mediante salidas (lección 4) o artefactos (lección 5).
El workflow útil más pequeño
Sección titulada «El workflow útil más pequeño».github/workflows/gha-01-hello.yml:
# GitHub Actions course, lesson 1: the smallest useful workflowname: "GHA 01: hello"
on: workflow_dispatch: push: branches: [main] paths: ['.github/workflows/gha-01-hello.yml']
jobs: hello: runs-on: ubuntu-latest steps: - name: Say hello run: echo "Hello from GitHub Actions"
- name: Where am I? run: | echo "Event: ${{ github.event_name }}" echo "Commit: ${{ github.sha }}" echo "Runner: $RUNNER_OS $RUNNER_ARCH" echo "Workdir: $(pwd)" ls -A
- name: Check out the repository uses: actions/checkout@v7
- name: Look again run: ls -A| Línea | Significado |
|---|---|
name: |
el nombre que se muestra en la pestaña Actions |
on: workflow_dispatch |
añade un botón Run workflow (y gh workflow run) |
on: push con branches y paths |
se ejecuta con un push a main que modifica este archivo |
jobs.hello.runs-on |
la imagen del runner: Ubuntu alojado por GitHub |
run: | |
un script de varias líneas, ejecutado por bash en Linux y macOS |
uses: actions/checkout@v7 |
la action checkout, versión mayor 7 |
La ejecución, step a step
Sección titulada «La ejecución, step a step»Hacer push del archivo inició una ejecución. Con la CLI de GitHub:
gh run list --workflow gha-01-hello.ymlgh run view 34845175861gh run view 34845175861 --log✓ main GHA 01: hello · 34845175861Triggered via push about 6 minutes ago
JOBS✓ hello in 4s (ID 103979246921)El log del step Where am I? (sin las marcas de tiempo):
##[group]Run echo "Event: push"echo "Event: push"echo "Commit: 61e7e4235b7bc138f2f35cd7f61195bdaed4b019"echo "Runner: $RUNNER_OS $RUNNER_ARCH"echo "Workdir: $(pwd)"ls -Ashell: /usr/bin/bash -e {0}Event: pushCommit: 61e7e4235b7bc138f2f35cd7f61195bdaed4b019Runner: Linux X64Workdir: /home/runner/work/learn/learnTres cosas que leer en él:
-
${{ github.event_name }}se reemplazó antes de que bash se ejecutara. El script que muestra el runner ya contienepush: las expresiones son una sustitución de texto hecha por GitHub.$RUNNER_OSes distinto: es una variable de entorno, expandida por bash. La lección 4 vuelve sobre esto, porque importa para la seguridad. -
ls -Ano imprimió nada. El directorio de trabajo/home/runner/work/learn/learnexiste pero está vacío. Después deactions/checkout, el mismo comando lista el repositorio:.git.gitattributes.github.gitignore.vscodeAGENTS.mdCLAUDE.mdREADME.md -
shell: /usr/bin/bash -e {0}: cadarun:se escribe en un script temporal y se ejecuta conbash -e, así que el step falla en el primer comando que falla.
El step Set up job te dice lo que te tocó:
Current runner version: '2.337.0'Ubuntu24.04.5LTSImage: ubuntu-24.04Version: 20260907.300.1GITHUB_TOKEN PermissionsContents: readMetadata: readPackages: readubuntu-latest es una etiqueta que cambia con el tiempo: en septiembre de 2026 es Ubuntu 24.04. Y el job recibió un GITHUB_TOKEN que solo puede leer el repositorio — la lección 7 explica cómo ampliarlo o restringirlo.
Trampa: un workflow inválido falla en cada push
Sección titulada «Trampa: un workflow inválido falla en cada push»Mi primera versión del workflow de la lección 3 tenía un error de YAML. La lista de ejecuciones mostraba la ruta del archivo en lugar del nombre del workflow, un fallo en 0 segundos y ningún job:
X main .github/workflows/gha-03-triggers.yml · 34845174629Triggered via push less than a minute ago
X This run likely failed because of a workflow file issue.La página web de la ejecución da el motivo: You have an error in your yaml syntax on line 41. Peor aún, el push siguiente — que no tocaba ese archivo, y el workflow tiene un filtro paths — produjo otra ejecución fallida: GitHub no puede leer los filtros de un archivo que no puede analizar. El culpable:
run: echo "cron: ${{ github.event.schedule }}"En YAML, un valor sin comillas no puede contener : (dos puntos seguidos de un espacio). Una comprobación local lo detecta antes del push:
python -c "import yaml; yaml.safe_load(open('.github/workflows/gha-03-triggers.yml'))"yaml.scanner.ScannerError: mapping values are not allowed here in ".github/workflows/gha-03-triggers.yml", line 41, column 24La corrección: un escalar de bloque (run: | y el comando en la línea siguiente), o poner todo el valor entre comillas.
Puntos clave
Sección titulada «Puntos clave»- Workflow → ejecución → jobs (un runner nuevo cada uno) → steps (
run:ouses:). - Un job empieza con un espacio de trabajo vacío:
actions/checkoutes casi siempre el primer step. - GitHub reemplaza
${{ }}antes de que se ejecute el script;$VARlo expande el shell. gh run list,gh run viewygh run view --logleen las ejecuciones sin salir de la terminal.- Un workflow que no se puede analizar aparece como un fallo de 0 segundos con el nombre de su archivo, en cada push.
Ejercicios
Sección titulada «Ejercicios»- En
gha-01-hello.yml, ¿qué imprimiríals -Aen el stepWhere am I?siactions/checkoutfuera el primer step?
Solución
Los archivos del repositorio (.git, .github, AGENTS.md…), como en el step Look again. El checkout clona en el directorio de trabajo, /home/runner/work/learn/learn, que es donde empieza cada run:.
- Añade un step que falle, ejecuta el workflow con
gh workflow run gha-01-hello.ymly encuentra el step fallido desde la terminal.
Solución
- name: Fail on purpose run: exit 1gh workflow run gha-01-hello.ymlgh run list --workflow gha-01-hello.yml --limit 1gh run view <run-id> --log-failed--log-failed imprime solo los logs de los steps fallidos, terminando con ##[error]Process completed with exit code 1.. Los steps posteriores no se ejecutan, salvo que tengan una condición como if: always() (lección 4).
- ¿Por qué
echo "Runner: $RUNNER_OS"funciona en un steprun:peroecho "cron: $X"rompió el YAML?
Solución
La primera línea está dentro de un escalar de bloque (run: |), donde YAML toma el texto tal cual. La segunda era un escalar plano (sin comillas) en la misma línea que run:, y un escalar plano no puede contener : . No tiene nada que ver con el shell: el archivo se rechaza antes de que arranque ningún runner.