Módulo 1: Why Cicd And Gitops

8. Proyecto: arrancando el pipeline de Andes Cargo

Descripción

Este es el proyecto que cierra el Módulo 1. No vas a escribir todavía ningún workflow real de Andes Cargo —eso empieza en el Módulo 2—, pero sí vas a preparar, con estructura real y verificada, exactamente el mismo repositorio andes-cargo-infra/ que dejaste terminado en terraform-and-iac-guide para que reciba su primer pipeline. Cuando termines esta lección, vas a tener ese proyecto inicializado como repositorio Git (si no lo estaba), con .github/workflows/ creado y listo para recibir su primer archivo, un .actrc que ya conoce la imagen de runner correcta, y LocalStack corriendo en tu máquina host — todo listo para que el Módulo 2 escriba el primer workflow que de verdad toca infraestructura.

Conexión con el módulo

Las lecciones 6 y 7 te dieron la herramienta y el ciclo completo, probados sobre un repositorio desechable que no tiene ningún vínculo con Andes Cargo. Este proyecto usa ese mismo conocimiento, pero sobre el repositorio real y permanente que vas a usar hasta el capstone del Módulo 8. El Módulo 2 abre con este mismo andes-cargo-infra/ ya existente, y agrega el primer workflow real — nada de lo que prepares aquí se vuelve a rehacer.


Punto de partida: lo que andes-cargo-infra/ ya trae

Si completaste terraform-and-iac-guide, tu carpeta andes-cargo-infra/ ya existe, con el layout que esa guía dejó terminado en su capstone — este proyecto no reescribe una sola línea de ese HCL:

   andes-cargo-infra/                          — HEREDADO, sin cambios

   versions.tf                                  provider AWS ~> 6.0
   providers.tf                                 mínimo, listo para tflocal
   variables.tf / locals.tf / outputs.tf         parametrización
   terraform.tfvars / dev.tfvars                 valores por ambiente
   .gitignore                                    protege *.tfstate, *.tfvars, etc.

   iam.tf                                        LambdaManifestProcessorRole, AppServerRole
   s3.tf                                          andes-cargo-shipment-docs
   lambda.tf                                      process-shipment-manifest
   dynamodb.tf                                    Shipments (PK shipmentId)

   modules/
   ├── s3-bucket/                                 módulo reutilizable
   └── iam-role/                                  módulo reutilizable

   .terraform.lock.hcl                            generado por terraform init
   terraform.tfstate                              generado por terraform apply (NUNCA versionado)

Lo que esta lección agrega es exclusivamente de la capa de pipeline, marcado abajo con ✚ — el resto de la tabla ya existía antes de que abrieras esta guía:

   andes-cargo-infra/                          — CUÁNDO SE AGREGA

   (todo lo de arriba)                          terraform-and-iac-guide

 ✚ .git/                                        Módulo 1 (esta lección, si no existía)
 ✚ .actrc                                       Módulo 1 (esta lección)
 ✚ .github/workflows/                           Módulo 1 (esta lección, vacío por ahora)

   .github/workflows/ci.yml                     Módulo 3
   .github/workflows/apply.yml                  Módulo 5
   .github/workflows/drift.yml                  Módulo 5
   .github/act-events/*.json                    Módulo 2
   .secrets                                     Módulo 2 (gitignorado desde esa lección)

Paso 1 — Confirmar (o inicializar) el repositorio Git

Párate en andes-cargo-infra/ y confirma si ya es un repositorio Git:

cd andes-cargo-infra
git status

Si tu proyecto ya es un repositorio Git desde terraform-and-iac-guide (algo que esa guía no exigía, pero que muchas personas hacen por costumbre), vas a ver el estado normal de tu rama. Si no lo es todavía, vas a ver este error real:

Qué esperar (literal, si el proyecto todavía no es un repositorio Git):

fatal: not a git repository (or any of the parent directories): .git

En ese caso, inicialízalo, fijando main como la rama por defecto —el nombre exacto que todo el resto de esta guía asume en cada on: push: branches: [main]—:

git init -b main

Qué esperar (literal):

Initialized empty Git repository in /ruta/a/andes-cargo-infra/.git/
git status

Qué esperar (literal) — cada archivo heredado de terraform-and-iac-guide aparece como "sin seguimiento" (untracked), porque el repositorio recién se creó y todavía no tiene ningún commit:

On branch main

No commits yet

Untracked files:
  (use "git add <file>..." to include in what will be committed)
	.gitignore
	dynamodb.tf
	iam.tf
	lambda.tf
	locals.tf
	modules/
	outputs.tf
	providers.tf
	s3.tf
	variables.tf
	versions.tf

nothing added to commit but untracked files present (use "git add" to track)

Fíjate en un detalle que vale la pena entender ahora, no descubrirlo por accidente más adelante: terraform.tfvars y dev.tfvars no aparecen en esta lista, aunque existen en la carpeta. No es un error — es el .gitignore heredado de terraform-and-iac-guide haciendo exactamente su trabajo: su línea *.tfvars ya está protegiendo esos archivos desde el primer git status, antes incluso de tu primer commit en este repositorio.


Paso 2 — .actrc, ahora en el proyecto real

En la lección 6 creaste .actrc en un laboratorio desechable. Ahora repite exactamente esa misma línea, pero en la raíz de andes-cargo-infra/ — porque, como ya sabes, act busca este archivo en el directorio de trabajo actual, no de forma global:

-P ubuntu-latest=catthehacker/ubuntu:act-latest

Este archivo va a crecer una vez más en el Módulo 3 (lección 5), cuando agregues una segunda línea —un --container-options que le permite al contenedor del job alcanzar el LocalStack que corre en tu host—. Por ahora, esta única línea es suficiente: sigue pineando la imagen de runner, evitando el prompt interactivo de la lección 6, exactamente igual que en tu laboratorio.


Paso 3 — Crear .github/workflows/

mkdir -p .github/workflows

Este comando no produce ninguna salida si funciona — confírmalo listando la estructura:

find . -maxdepth 1

Qué esperar (literal, con .git/ ya inicializado y .actrc ya creado):

.
./.actrc
./.git
./.github
./.gitignore
./dev.tfvars
./dynamodb.tf
./iam.tf
./lambda.tf
./locals.tf
./modules
./outputs.tf
./providers.tf
./s3.tf
./terraform.tfvars
./variables.tf
./versions.tf

.github/workflows/ todavía está completamente vacía —ni siquiera aparece como una entrada separada en git status, porque Git, a diferencia de una carpeta de sistema de archivos común, no rastrea directorios vacíos — solo archivos. Va a aparecer en tu próximo git status recién cuando el Módulo 2 agregue el primer archivo .yml dentro. No es un error ni algo que debas forzar ahora: es, literalmente, "listo para recibir contenido", no "contenido ya existente".

Confirma que act ya reconoce este proyecto, aunque todavía no tenga ningún workflow:

act -l

Qué esperar (literal) — solo el encabezado, sin filas, sin errores ni el prompt interactivo (porque .actrc ya resolvió qué imagen usar):

Stage  Job ID  Job name  Workflow name  Workflow file  Events

Paso 4 — Commitear el bootstrap de la capa de pipeline

git add -A
git commit -m "Bootstrap CI/CD layer: initialize git repository, .actrc, and .github/workflows/"

Qué esperar (literal):

[main (root-commit) ce6efa5] Bootstrap CI/CD layer: initialize git repository, .actrc, and .github/workflows/
 12 files changed, ...

El hash del commit (ce6efa5 en esta ejecución) es, como el sha que viste en la lección 7, variable — el tuyo va a ser distinto, porque depende del contenido exacto de tus archivos y del momento exacto del commit. Lo que no varía es la estructura: doce archivos heredados de terraform-and-iac-guide más .actrc, todos entrando al historial de Git por primera vez en este mismo commit.

git log --oneline

Qué esperar (representativo en el hash, literal en la estructura) — un único commit, el primero de este repositorio:

ce6efa5 Bootstrap CI/CD layer: initialize git repository, .actrc, and .github/workflows/
git status

Qué esperar (literal):

On branch main
nothing to commit, working tree clean

Paso 5 — LocalStack, arrancado limpio en el host

terraform-and-iac-guide (Módulo 1, lección 5) ya te enseñó, con detalle completo, por qué el plan Hobby de LocalStack no persiste recursos entre reinicios del contenedor, y por qué esta familia de guías no asume que tu sesión anterior sigue viva. Esta lección no vuelve a explicar ese mecanismo — solo repite el arranque, exactamente igual que entonces, porque el resto de esta guía (desde el Módulo 3 en adelante) necesita LocalStack corriendo en tu host mientras act corre los jobs en sus propios contenedores por separado.

Exporta tu token y arranca el contenedor:

export LOCALSTACK_AUTH_TOKEN=<TU_AUTH_TOKEN>
docker run \
  --rm -d \
  --name localstack_main \
  -p 127.0.0.1:4566:4566 \
  -p 127.0.0.1:4510-4559:4510-4559 \
  -e LOCALSTACK_AUTH_TOKEN=${LOCALSTACK_AUTH_TOKEN:?} \
  -v /var/run/docker.sock:/var/run/docker.sock \
  localstack/localstack

Si te falta el token (lo dejaste vacío, o no lo exportaste en esta sesión de shell), el contenedor arranca y se cierra en segundos con este mensaje —reproducido literal, verificado hoy contra la imagen actual de LocalStack, el mismo error que documentó terraform-and-iac-guide—:

LocalStack version: 2026.7.3
LocalStack build date: 2026-08-12
LocalStack build git hash: 8f10c66d8

Localstack returning with exit code 55. Reason: 
===============================================
License activation failed! 🔑❌

Reason: No credentials were found in the environment. Please make sure to either set the LOCALSTACK_AUTH_TOKEN variable to a valid auth token. If you are using the CLI, you can also run `localstack auth set-token`.

Due to this error, Localstack has quit. LocalStack pro features can only be used with a valid license.

Con el token correctamente exportado, revisa los logs hasta la señal de arranque completo:

docker logs -f localstack_main

Qué esperar (representativo) — mismo formato que confirmaron las dos guías anteriores, sin una ejecución en vivo contra un token válido en este momento; la línea Ready. es la señal fija que confirma el arranque:

LocalStack version: 2026.7.3
LocalStack build date: 2026-08-12

Starting LocalStack on host 0.0.0.0 ...
Waiting for all LocalStack services to be ready
Ready.

Sal del seguimiento con Ctrl+C (el contenedor sigue corriendo), y confirma tu identidad dentro del laboratorio, exactamente con el comando que ya conoces:

awslocal sts get-caller-identity

Qué esperar (representativo, mismo formato ya verificado en las dos guías anteriores):

{
    "UserId": "AKIAIOSFODNN7EXAMPLE",
    "Account": "000000000000",
    "Arn": "arn:aws:iam::000000000000:root"
}

"Account": "000000000000" — el mismo account ID fijo de siempre, el mismo que vas a ver, ahora también, en la salida de cada terraform plan que corras dentro de un job de act a partir del Módulo 3.


Cierre del Módulo 1

Completaste el módulo que abre esta guía. Repasa lo que te llevas:

  • El problema, nombrado con precisión: un apply manual no deja registro de quién lo corrió, expone credenciales de larga vida en una laptop personal, y depende de que una persona específica esté disponible — revisitado con el incidente del destroy de Claude Code desde un ángulo nuevo: un pipeline no evita la negligencia, pero sí la hace auditable (lección 2).
  • El vocabulario, sin ambigüedad: CI (¿rompe algo?), CD-entrega (¿está listo, y quién dice que sí?) y CD-despliegue (¿ya está en vivo?) — tres cosas, una sigla (lección 3). GitOps como principio —cuatro propiedades, no una herramienta— con origen exacto en Weaveworks, 2017 (lección 4).
  • La herramienta, con honestidad de mercado: GitHub Actions elegido por fricción cero y por act, sin pretender que "gana" sobre Jenkins, que domina el stack español relevado 5 de 13 contra ~3 de 13 (lección 5).
  • El laboratorio, instalado y verificado: act 0.2.89 corriendo en tu máquina (lección 6), conectado a Docker, con un primer workflow completo corrido de punta a punta y dos errores reales ya reconocidos (lección 7).
  • El proyecto preparado: andes-cargo-infra/, con Git inicializado, .actrc pineado, .github/workflows/ creado y vacío, y LocalStack corriendo — listo para su primer workflow real.

Qué viene después

El Módulo 2 toma este mismo andes-cargo-infra/ y diseca la sintaxis completa de un workflow de GitHub Actions: on/jobs/steps/runs-on/uses/with/env, sobre un workflow real, no un fragmento aislado. Vas a aprender a simular eventos con act -e —escribiendo tu propio pr-event.json con un número de PR fijo, sin necesitar una cuenta de GitHub real— y a pasar secretos con act --secret-file .secrets. El proyecto de ese módulo es el primer workflow que de verdad toca Andes Cargo: hello-andes-cargo.yml, con un step que confirma, desde dentro del contenedor de act, que puede alcanzar el LocalStack que corre en tu host.


Errores comunes

Escribir un workflow real de Andes Cargo en esta lección, "para adelantar" (de flujo). Qué pasa: alguien, con el ciclo de la lección 7 todavía fresco, agrega directamente un archivo .yml con un step de terraform plan dentro de .github/workflows/, saltándose el resto de este módulo y el Módulo 2. Por qué pasa: el impulso de "ya sé cómo hacerlo, ¿para qué esperar?" es comprensible después de la lección 7. Cómo detectarlo: si tu .github/workflows/ ya tiene un archivo antes de terminar el Módulo 2. Cómo corregirlo: no pasa nada grave técnicamente, pero te vas a saltar la anatomía completa de un workflow, cómo simular eventos sin una cuenta de GitHub, y cómo pasar secretos de forma segura — todo contenido que el Módulo 2 construye, a propósito, antes de que tu primer workflow real de Andes Cargo exista.

Asumir que andes-cargo-infra/ necesita convertirse en un repositorio nuevo, distinto (conceptual). Qué pasa: alguien crea una carpeta separada, andes-cargo-infra-pipeline/ o similar, para esta guía, en vez de usar la carpeta exacta que terraform-and-iac-guide dejó terminada. Por qué pasa: se siente "más limpio" separar el trabajo de una guía del de la otra. Cómo detectarlo: si tienes dos carpetas distintas con nombres parecidos, cada una con una parte del proyecto. Cómo corregirlo: el diseño completo de esta guía asume que es el mismo repositorio, sin excepción — el HCL de terraform-and-iac-guide y el YAML de esta guía tienen que convivir en la misma carpeta, porque el pipeline que vas a construir automatiza exactamente ese HCL. Si separaste las carpetas, mueve todo el contenido de vuelta a una sola antes de seguir.

Preocuparse porque .github/workflows/ "no aparece" en git status después de crearla (de expectativa, aclarado en el Paso 3). Qué pasa: alguien crea la carpeta vacía, corre git status, y no la ve listada — y asume que algo salió mal. Por qué pasa: en la mayoría de los sistemas de archivos, una carpeta "existe" independientemente de si tiene contenido; Git no funciona así. Cómo detectarlo: si esperabas ver .github/ en la lista de archivos sin seguimiento, y no está. Cómo corregirlo: no hay nada que corregir — Git solo rastrea archivos, nunca directorios vacíos por sí mismos. La carpeta va a aparecer en tu próximo git status en cuanto el Módulo 2 agregue el primer archivo .yml dentro de ella.


Ejercicios

Ejercicio 1 — Explica por qué terraform.tfvars no aparece en git status. Sin mirar esta lección, explica a un colega por qué, al correr git status por primera vez en un andes-cargo-infra/ recién inicializado, los archivos terraform.tfvars y dev.tfvars no aparecen en la lista de archivos sin seguimiento, aunque existen en la carpeta.

Ver solución

El .gitignore heredado de terraform-and-iac-guide incluye la línea *.tfvars, que le indica a Git que ignore por completo cualquier archivo con esa extensión — Git ni siquiera los considera "sin seguimiento", los excluye directamente de cualquier listado o posibilidad de git add, salvo que se fuerce explícitamente con git add -f. Esto es intencional y correcto: los archivos .tfvars suelen contener valores específicos de un ambiente que no deberían viajar al historial de un repositorio compartido.

Ejercicio 2 — Predice el resultado de act -l en dos momentos distintos. ¿Qué esperas que muestre act -l en andes-cargo-infra/ al final de esta lección (Módulo 1), y qué esperas que muestre al final del Módulo 2, después de que agregues hello-andes-cargo.yml? Justifica la diferencia.

Ver solución

Al final de esta lección: solo el encabezado de la tabla (Stage Job ID Job name Workflow name Workflow file Events), sin ninguna fila — .github/workflows/ existe, pero está vacía, así que no hay ningún job que act pueda listar. Al final del Módulo 2: una fila nueva, describiendo el job de hello-andes-cargo.yml, con su nombre, el archivo donde vive, y el evento que lo dispara. La diferencia no es un cambio de comportamiento de act — es, simplemente, que ahora existe contenido real dentro de la carpeta que antes estaba vacía.

Ejercicio 3 — Justifica git init -b main en vez de git init a secas. Un colega te pregunta por qué esta lección usa específicamente git init -b main, en vez del git init simple que usaste en otras guías. Explícale, en dos o tres frases, la razón.

Ver solución

Una respuesta completa suena, más o menos, así: "Dependiendo de la versión de Git y de la configuración de cada máquina, git init a secas puede crear la rama inicial con el nombre master en vez de main. Todo workflow de esta guía —ci.yml, apply.yml, drift.yml— asume explícitamente main como el nombre de la rama principal en su bloque on: push: branches: [main]; si el repositorio real usara master, ninguno de esos triggers dispararía nunca. Fijar el nombre de la rama de forma explícita con -b main en el momento de inicializar evita ese desajuste desde el principio, en vez de descubrirlo cuando un workflow del Módulo 3 simplemente no corre."


Resumen y siguiente paso

En este proyecto preparaste andes-cargo-infra/ para recibir su primer pipeline: confirmaste (o inicializaste) el repositorio Git con main como rama por defecto, creaste .actrc con la imagen de runner pineada, y creaste .github/workflows/ —vacía, lista para su primer archivo—. Confirmaste con git log y git status que el bootstrap quedó commiteado, y arrancaste LocalStack en tu host, reproduciendo el mismo patrón de honestidad de las dos guías anteriores: literal donde no depende de un token, representativo donde sí, nunca inventado.

Antes de avanzar deberías poder: explicar de memoria qué agrega esta lección sobre lo que ya traía andes-cargo-infra/ de terraform-and-iac-guide; justificar por qué .github/workflows/ no aparece en git status estando vacía; y decir, sin dudar, por qué el nombre de la rama principal tiene que ser main, no master, para que el resto de esta guía funcione.

Con esto, el Módulo 1 queda cerrado. Tienes el problema entendido, el vocabulario preciso, la herramienta instalada y probada, y el proyecto real preparado para su primer workflow.

Siguiente módulo: la anatomía completa de un workflow de GitHub Actions — vas a diseccionar on/jobs/steps/runs-on/uses/with/env sobre un workflow real, vas a simular eventos sin una cuenta de GitHub con act -e, y el proyecto de ese módulo es el primer workflow que de verdad toca Andes Cargo.

Recursos

  1. Git Docs — git init — documentación oficial, incluida la opción -b para fijar el nombre de la rama inicial.
  2. nektosact.com — User Guide — referencia de act -l, usada en esta lección para confirmar el proyecto vacío.
  3. LocalStack Docs — Auth Token — la fuente del error de credenciales reproducido en esta lección.
  4. terraform-and-iac-guide, Módulo 1, lección 5 (05-hands-on-installing-terraform-and-a-clean-start.md) — la explicación completa del arranque limpio de LocalStack, asumida y no repetida aquí.