Módulo 1: Why Cicd And Gitops
7. Manos a la obra: tu primer workflow local, de punta a punta
Descripción
Este es el momento donde todo lo que leíste en las lecciones 1 a 5, e instalaste en la lección 6, se convierte en un workflow corriendo de verdad. Vas a crear un repositorio desechable —fuera de andes-cargo-infra/, que arrancas recién en la lección 8—, escribir un workflow de "hola mundo", y correrlo con act: listar sus jobs, dispararlo con un evento simulado, y leer su salida completa. También vas a reproducir, a propósito, dos errores reales de act — para que los reconozcas de inmediato cuando aparezcan más adelante en esta guía, no la primera vez que te bloqueen sin previo aviso.
Conexión con el módulo
Esta es la lección bisagra de todo el módulo. La lección 6 te dio la herramienta lista; esta lección la usa, por primera vez, sobre un workflow real. Todo lo que escribas aquí es intencionalmente desechable —un "hola mundo" sin ningún vínculo con Andes Cargo— porque el objetivo no es empezar el proyecto real todavía (eso es la lección 8), es que el ciclo completo —escribir YAML, listar jobs, dispararlos, leer la salida— te quede grabado antes de tocar infraestructura que le importa a alguien.
El ciclo completo, de un vistazo
sequenceDiagram
participant Tú
participant act
participant Docker
Tú->>act: act -l
act-->>Tú: lista de jobs detectados en .github/workflows/
Tú->>act: act push
act->>Docker: docker pull catthehacker/ubuntu:act-latest
Docker-->>act: imagen lista
act->>Docker: crea un contenedor efímero para el job
Docker->>Docker: corre cada step, en orden
Docker-->>act: salida de cada step
act-->>Tú: "Job succeeded" (o el error, si algo falló)
Dos comandos, cada uno con un trabajo específico: act -l lee el YAML y te dice qué jobs existen y qué eventos los disparan, sin ejecutar nada. act push (o act a secas, si el workflow escucha push) simula ese evento y corre el job completo, de verdad, dentro de un contenedor.
El proyecto: un repositorio, un workflow
Crea una carpeta nueva para este ejercicio, fuera de cualquier proyecto real:
mkdir act-lab && cd act-lab
git init
mkdir -p .github/workflows
.actrc — la misma línea que ya escribiste en la lección 6, ahora en este proyecto específico (recuerda: act la busca en el directorio de trabajo actual, no de forma global):
-P ubuntu-latest=catthehacker/ubuntu:act-latest
.github/workflows/hello.yml — el único workflow de esta lección:
name: hello-world
on:
push:
branches: [main]
jobs:
say-hello:
runs-on: ubuntu-latest
steps:
- name: Print a greeting
run: echo "Hello from act, running Andes Cargo's first workflow"
- name: Show reproducibility fields
run: |
echo "run_id=${{ github.run_id }}"
echo "run_number=${{ github.run_number }}"
echo "sha=${{ github.sha }}"
Fíjate en la estructura, aunque el Módulo 2 la diseccione a fondo: on dice cuándo corre (en cada push a main), jobs agrupa el trabajo (un solo job, say-hello), runs-on dice en qué imagen (ubuntu-latest, la que .actrc mapea a catthehacker/ubuntu:act-latest), y steps es la secuencia de comandos, en orden. El segundo step imprime tres valores que vas a usar para confirmar, con tus propios ojos, la tabla de reproducibilidad del diseño de esta guía: github.run_id y github.run_number (fijos bajo act), y github.sha (el commit real de tu repositorio, que va a variar).
Confirma que act detecta el workflow antes de correr nada:
git add -A
git commit -m "first workflow"
act -l
Qué esperar (salida literal, ejecutada para escribir esta lección):
Stage Job ID Job name Workflow name Workflow file Events
0 say-hello say-hello hello-world hello.yml push
Esta tabla es puramente informativa: te dice qué jobs existen, en qué archivo, y qué evento los dispara — sin ejecutar un solo comando todavía.
Paso 1 — Disparar el workflow con act push
act push
Qué esperar (salida literal, ejecutada para escribir esta lección — nota antes de leerla: en una Mac con chip Apple Silicon vas a ver, además, la advertencia de arquitectura de la lección 6; se omite aquí por brevedad, ya la conoces):
[hello-world/say-hello] ⭐ Run Set up job
[hello-world/say-hello] 🚀 Start image=catthehacker/ubuntu:act-latest
[hello-world/say-hello] 🐳 docker pull image=catthehacker/ubuntu:act-latest platform= username= forcePull=true
[hello-world/say-hello] using DockerAuthConfig authentication for docker pull
[hello-world/say-hello] 🐳 docker create image=catthehacker/ubuntu:act-latest platform= entrypoint=["tail" "-f" "/dev/null"] cmd=[] network="host"
[hello-world/say-hello] 🐳 docker run image=catthehacker/ubuntu:act-latest platform= entrypoint=["tail" "-f" "/dev/null"] cmd=[] network="host"
[hello-world/say-hello] 🐳 docker exec cmd=[node --no-warnings -e console.log(process.execPath)] user= workdir=
[hello-world/say-hello] ✅ Success - Set up job
[hello-world/say-hello] ⭐ Run Main Print a greeting
[hello-world/say-hello] 🐳 docker exec cmd=[bash -e /var/run/act/workflow/0] user= workdir=
[hello-world/say-hello] | Hello from act, running Andes Cargo's first workflow
[hello-world/say-hello] ✅ Success - Main Print a greeting [57.549834ms]
[hello-world/say-hello] ⭐ Run Main Show reproducibility fields
[hello-world/say-hello] 🐳 docker exec cmd=[bash -e /var/run/act/workflow/1] user= workdir=
[hello-world/say-hello] | run_id=1
[hello-world/say-hello] | run_number=1
[hello-world/say-hello] | sha=d379bd25a7179b3b0e1f598cc891a8ee62abc04a
[hello-world/say-hello] ✅ Success - Main Show reproducibility fields [60.556458ms]
[hello-world/say-hello] ⭐ Run Complete job
[hello-world/say-hello] Cleaning up container for job say-hello
[hello-world/say-hello] ✅ Success - Complete job
[hello-world/say-hello] 🏁 Job succeeded
Lee esta salida con la misma atención que le dedicarías a un terraform plan. Cada línea con 🐳 es una operación real de Docker —act no la simula, la ejecuta—; el docker create/docker run con entrypoint=["tail" "-f" "/dev/null"] es el truco técnico que usa act para mantener el contenedor vivo mientras corre cada step dentro de él, uno por uno, con docker exec. Cada ✅ marca un paso exitoso; si cualquiera fallara, act se detiene ahí y reporta el fallo, exactamente como un runner real de GitHub.
La tabla de reproducibilidad, confirmada con tus propios ojos
Mira las tres líneas que imprimió el segundo step:
| Campo | Valor de esta corrida | ¿Fijo o variable? |
|---|---|---|
run_id | 1 | Fijo — act no incrementa este valor entre corridas locales, a diferencia de GitHub real |
run_number | 1 | Fijo, misma razón |
sha | d379bd25a7179b3b0e1f598cc891a8ee62abc04a | Variable — es el commit real de git rev-parse HEAD en tu repositorio; el tuyo va a ser un hash completamente distinto |
Esta es, literalmente, la distinción de honestidad más fina de esta guía, confirmada con un comando real en vez de solo leída en el diseño: cada vez que corras act push sobre este mismo repositorio, run_id y run_number van a seguir siendo 1 — pero si corres el workflow después de un nuevo commit, sha va a cambiar. Ningún bloque "Qué esperar" de esta guía presenta un SHA de commit como si fuera un valor fijo — siempre está marcado como variable, exactamente como aquí.
Paso 2 — Reproducir dos errores reales, a propósito
Error 1 — YAML mal indentado
Rompe la indentación de tu workflow a propósito. Crea .github/workflows/broken.yml:
name: broken-workflow
on:
push:
jobs:
say-hello:
runs-on: ubuntu-latest
steps:
- run: echo "this yaml is broken"
Fíjate en el error: runs-on perdió su indentación respecto a say-hello. Corre:
act push
Qué esperar (salida literal, reproducida para esta lección):
Error: workflow is not valid. 'broken.yml': yaml: line 7: mapping values are not allowed in this context
act valida la sintaxis YAML antes de intentar ejecutar nada — ni siquiera llega a crear un contenedor. El mensaje te da el archivo y la línea exactos, aunque el número de línea puede no coincidir exactamente con dónde "se ve" el error a simple vista (YAML es sensible a indentación de una forma que a veces hace que el error se reporte una línea antes o después de donde el ojo humano lo detecta primero). Borra broken.yml antes de seguir — no lo necesitas para el resto de esta lección.
Error 2 — Un evento que no dispara ningún job
Tu hello.yml solo escucha push. ¿Qué pasa si le pides a act que simule un evento distinto, que ningún job de este repositorio espera?
act pull_request
Qué esperar (salida literal, reproducida para esta lección):
Error: Could not find any stages to run. View the valid jobs with `act --list`. Use `act --help` to find how to filter by Job ID/Workflow/Event Name
Este no es un error de sintaxis —tu YAML sigue siendo válido—, es act diciéndote, con precisión, que ningún job de este repositorio está configurado para reaccionar al evento pull_request. Es el mismo tipo de situación que vas a encontrar en el Módulo 3, cuando ci.yml escuche pull_request y apply.yml escuche push — si le pides a act el evento equivocado para el workflow equivocado, este es exactamente el mensaje que vas a ver.
Errores comunes
Confundir el error de YAML con un problema de Docker (de diagnóstico). Qué pasa: alguien ve Error: workflow is not valid y empieza a revisar si Docker está corriendo, si la imagen se descargó bien, etc. Por qué pasa: cualquier error de act se siente, a primera vista, como "algo con los contenedores salió mal". Cómo detectarlo: si el mensaje incluye la frase workflow is not valid seguida de un nombre de archivo y un número de línea. Cómo corregirlo: este error ocurre antes de que act toque Docker en absoluto — es un error de parseo de YAML, resuelto editando el archivo indicado, nunca reiniciando contenedores.
Olvidar que act -l no ejecuta nada (de expectativa). Qué pasa: alguien corre act -l, ve la tabla de jobs, y espera que eso signifique que el workflow ya corrió. Por qué pasa: la salida se parece, superficialmente, a un resumen de ejecución. Cómo detectarlo: si esperabas ver el echo de tu workflow después de correr act -l, y no aparece en ningún lado. Cómo corregirlo: -l es de "list" (listar) — es puramente informativo, lee el YAML sin correr un solo step. Para ejecutar de verdad, necesitas act push (o act a secas, o act -j <job-id> para un job específico).
Esperar que act pull_request funcione sobre cualquier repositorio (conceptual, ver Error 2 arriba). Qué pasa: alguien asume que act <evento> es un comando genérico que siempre corre "el workflow", sin importar qué evento escuche ese workflow específico. Por qué pasa: es fácil pensar en act como "el botón de correr todo" en vez de "el simulador de un evento específico". Cómo detectarlo: si te sorprende el error Could not find any stages to run al pedir un evento que tu YAML no declara en su bloque on. Cómo corregirlo: revisa siempre act -l primero — la columna Events te dice exactamente qué evento tienes que pedirle a act para que ese job corra.
Ejercicios
Ejercicio 1 — Corre el ciclo de memoria. Sin mirar esta lección, escribe los dos comandos que usaste hoy, en orden, y qué hace cada uno.
Ver solución
1. act -l — lee el YAML de .github/workflows/ y muestra qué jobs existen, en qué archivo, y qué evento los dispara, sin ejecutar nada. 2. act push (o act <evento> en general) — simula ese evento específico y corre, de verdad, dentro de un contenedor Docker, cada job que lo escuche, step por step, en orden.
Ejercicio 2 — Predice qué campo cambia. Si corres act push sobre el mismo repositorio de esta lección tres veces seguidas, sin hacer ningún commit nuevo entre medio, ¿qué esperas que pase con run_id, run_number y sha en cada corrida?
Ver solución
run_id y run_number van a seguir siendo 1 las tres veces — act no los incrementa entre corridas locales, a diferencia de GitHub real, donde cada corrida de un workflow tiene un run_id distinto y creciente. sha también va a ser idéntico en las tres corridas, porque no hiciste ningún commit nuevo entre medio — github.sha bajo act refleja el HEAD real de tu repositorio Git local en el momento de la corrida, y si el HEAD no cambió, el valor tampoco cambia. El sha solo cambiaría si hicieras un nuevo commit antes de volver a correr act push.
Ejercicio 3 — Diagnostica el evento equivocado. Un compañero te muestra este mensaje: "corrí act workflow_dispatch sobre mi repositorio y me dio Could not find any stages to run, pero estoy seguro de que mi YAML no tiene ningún error de sintaxis". ¿Qué comando le pedirías que corra primero para diagnosticar el problema, y qué esperarías ver ahí?
Ver solución
Le pedirías que corra act -l primero. Ese comando muestra, en la columna Events, exactamente qué evento(s) disparan cada job de su repositorio. Si la columna dice push (como en el hello.yml de esta lección) y él pidió workflow_dispatch, el diagnóstico es inmediato: no hay ningún error de sintaxis —tenía razón en eso—, simplemente le pidió a act un evento que ningún job de ese repositorio escucha. La solución es o bien correr act push (el evento correcto para ese YAML), o bien agregar workflow_dispatch al bloque on del workflow si de verdad quiere poder dispararlo manualmente — patrón que el Módulo 2 de esta guía cubre en detalle.
Resumen y siguiente paso
En esta lección corriste, por primera vez, un workflow completo con act: listaste sus jobs con act -l (literal), lo disparaste con act push y leíste su salida completa, línea por línea, incluidas las operaciones reales de Docker por debajo. Confirmaste con tus propios ojos la tabla de reproducibilidad del diseño de esta guía: run_id y run_number fijos en 1, sha variable según tu commit real. También reprodujiste, de forma literal, los dos errores más comunes de esta capa: YAML mal indentado, y pedir un evento que ningún job escucha.
Antes de avanzar deberías poder: escribir un workflow mínimo de "hola mundo" de memoria; explicar la diferencia entre act -l y act push; y leer el mensaje Could not find any stages to run sin confundirlo con un problema de Docker.
Tienes el ciclo completo grabado. Lo único que falta es empezar el proyecto real. La lección 8 —el proyecto de este módulo— prepara andes-cargo-infra/, el repositorio que vas a usar, sin reescribirlo, hasta el capstone del Módulo 8.
Recursos
- nektosact.com — User Guide — documentación oficial de
act -l,act push, y cómo filtrar por evento o job. - GitHub Docs — Understanding GitHub Actions — la anatomía de
on/jobs/steps/runs-onque empezaste a usar en esta lección, diseccionada a fondo en el Módulo 2. - YAML Spec — Indentation — la especificación oficial de YAML, la fuente de por qué la indentación no es una preferencia de estilo, sino sintaxis obligatoria.