Module 7: Gitops Beyond Terraform
8. Project: Andes Cargo's tooling decision
Description
This project doesn't write a single line of new YAML. Instead, it turns this module's seven previous lessons into the kind of document a real engineering team produces when they make a tooling decision other people are going to inherit: an ADR (Architecture Decision Record). You're going to write the complete ADR where Andes Cargo justifies, in writing, why it chose GitHub Actions over Jenkins and GitLab CI —with the real market evidence cited, not an unsupported opinion— and what would change the day the company deploys its own application on a Kubernetes cluster. It's a real, complete document, the same kind kept in any serious team's repository, versioned alongside the code the decision affects.
Connection to the module
This project is the whole module's synthesis, not an isolated lesson. The market evidence it cites comes from lesson 2; so does the syntax comparison. The section on what would change with Kubernetes precisely revisits lessons 3 and 4 (ArgoCD/Flux, push vs. pull). The ADR is, itself, this module's executed deliverable —EXECUTED (the document), as this guide's design labels it—: it doesn't run code, but it's a real artifact, written in full, not described in the abstract. Module 8, the whole guide's capstone, doesn't write another ADR — it picks the complete technical pipeline (ci.yml, apply.yml, drift.yml, the guardrail) back up for a final end-to-end walkthrough.
Analogy: a ship captain's logbook
Imagine a ship's captain who, at some point in a long voyage, decides to change course —avoiding a storm zone, even if it means more days at sea—. A careless captain simply changes course and keeps sailing: the ship arrives, sooner or later, but nobody who comes aboard afterward —a new officer, an inspector, the captain themselves six months later, memory already blurry— can know why that decision was made, or whether the conditions that motivated it are still valid. A serious captain, instead, writes in the logbook: the date, the exact position, what information was available at that moment (the weather report, the cargo aboard, the fuel remaining), what they decided, and what they expected to happen as a result. That logbook doesn't change the ship's course —the ship already turned—, but it turns a decision that lived only inside the captain's head into something anyone can read and understand, even years later.
An ADR is, precisely, a technical decision's logbook. It isn't the code that implements the decision —you already built that, in Modules 2 through 6—, it's the record of why that decision was made, with what information, and what was expected in return. For a logistics company like Andes Cargo, the metaphor isn't even a stretch: it's, literally, the same kind of document a real cargo captain would keep about route decisions.
What an ADR is, precisely (the format, not just the idea)
An ADR follows a standard format, originally proposed by Michael Nygard in 2011 and widely adopted since then in the software industry: a short text file (one or two pages, per the original format), versioned alongside the code, with five fixed sections:
- Title — a short noun phrase identifying the decision.
- Status —
proposed,accepted,deprecated, orsuperseded by [ADR-XXXX]. - Context — the forces at play: technical, business, team-related — without taking a side yet, just describing the situation.
- Decision — the answer, in active voice: "we're going to do X," not "X could be considered."
- Consequences — the resulting context after applying the decision, all the consequences, not just the positive ones.
The convention for where these files live, popularized by tools like adr-tools, is a doc/adr/ folder at the repository root, with sequentially numbered files: 0001-short-title.md, 0002-next-decision.md, and so on — a history of decisions, not a single document rewritten every time.
Andes Cargo's complete ADR
This is the document as it would live at andes-cargo-infra/doc/adr/0001-choosing-cicd-tooling.md, if you wrote it today, with this module's lessons as the direct source:
# ADR-0001: Choosing GitHub Actions as the CI/CD tool for andes-cargo-infra
## Status
Accepted — August 2026
## Context
andes-cargo-infra/ declares, in Terraform, Andes Cargo's complete infrastructure on AWS:
the andes-cargo-shipment-docs bucket, the Shipments table, the LambdaManifestProcessorRole
and AppServerRole roles, and the process-shipment-manifest function. Until now, any change
to that infrastructure got applied by hand, from the laptop of whoever wrote it, with no
systematic review and no record of who ran what command. The team needs to automate that
process: verify every proposed change, hold it for review before merging, and automatically
apply only what's already approved.
We evaluated four tools: GitHub Actions, GitLab CI, CircleCI, and Jenkins. This ecosystem's
market audit (VALIDACION.md, jul-2026) is clear about the real evidence from the Spanish
stack: Jenkins shows up in 5 of 13 surveyed job postings, almost double GitHub Actions
(~3 of 13) — IRIUM, Apptiva, and MediaStream name Jenkins explicitly, versus CookUnity and
EarnIn, which name GitHub Actions.
Team constraints: no budget or dedicated staff to maintain our own CI server; the code
already lives on GitHub; being able to test the complete pipeline locally, with no spend on
hosted runners, is a priority while the team learns this layer for the first time.
## Decision
We're going to use GitHub Actions as the CI/CD tool for andes-cargo-infra/, with three
concrete justifications, none of which is "it dominates the market":
1. Zero friction: the configuration lives in the same repository, in `.github/workflows/`,
with no separate server to install or maintain — unlike Jenkins, which in the vast
majority of real cases requires its own infrastructure.
2. `act` (nektos/act) exists: it lets us run the same YAML locally, in Docker, for free,
with no GitHub account or hosted runner minutes — the piece that makes it possible for
the team to learn and test the complete pipeline at $0 cost while getting familiar with
this layer for the first time.
3. The repository already lives on GitHub: there's no migration cost to another
version-control platform to adopt its native CI/CD.
We explicitly acknowledge that this decision doesn't follow the dominant market signal:
Jenkins shows up more frequently than GitHub Actions in the surveyed Spanish stack's job
postings. The team commits to keeping documented, as a reference (ADR-0001, Appendix:
equivalent Jenkins syntax), how the same pipeline would look in a Jenkinsfile, so as not to
lose that reference if a future client or employer requires it.
## Consequences
Positive:
- The complete pipeline (fmt/validate/plan on every PR, apply on every merge, scheduled
drift detection) can be built and fully tested locally, at zero cost, before ever
touching a real AWS account.
- The team's learning curve is shorter than Jenkins's, because there's no need to learn
Groovy or administer a server in addition to the pipeline itself.
- Native integration with GitHub Pull Requests: PR comments, required checks, and branch
protection (future ADR, see Module 6 of the internal reference guide) work with no
additional third-party configuration.
Negative / accepted risks:
- The team ends up, in practice, less aligned with the tool that shows up most in the
surveyed Spanish job market (Jenkins, 5 of 13 versus ~3 of 13 for GitHub Actions) — an
employability risk the team decides to accept, partially mitigated by keeping the
equivalent Jenkins syntax documented as a reference.
- Dependency on GitHub as a platform: migrating version control to GitLab or Bitbucket in
the future would require rewriting all of `.github/workflows/` in the new platform's
syntax — the underlying pattern (plan/review/apply) would transfer, but not a single
configuration file.
- `act` has documented limitations (it doesn't implement OIDC token issuance; it ignores
`environment:` for protection purposes) that require certain security pieces to be
verified against a real GitHub account before being considered fully tested, not just
simulated locally.
## What would change if Andes Cargo had a Kubernetes cluster
This decision covers, exclusively, CI/CD for infrastructure declared via Terraform against
API-managed AWS services (S3, DynamoDB, IAM, Lambda). The day Andes Cargo deploys its own
application —for example, a shipment-tracking API with replicas running behind a load
balancer— on an EKS cluster, the right tool for THAT specific deployment would no longer
necessarily be GitHub Actions pushing a `kubectl apply` from an external pipeline
(push-based). The option most aligned with the industry's standard practices for
Kubernetes would be adopting ArgoCD or Flux: a pull-based GitOps operator that runs inside
the cluster itself, eliminates the need for write credentials traveling from an external
system, and continuously reconciles the cluster's state against Git (including
self-correction, not just detection).
This ADR doesn't decide that migration ahead of time — it limits itself to documenting
that, if that day comes, the tooling decision for application deployment on Kubernetes
deserves its own ADR (ADR-000N: Choosing ArgoCD/Flux for application GitOps on EKS),
evaluated with the same market and scope honesty as this document.
## Alternatives considered and rejected
- **GitLab CI**: rejected because the code doesn't live on GitLab; adopting it would mean
migrating version-control platforms with no technical reason justifying it today.
- **CircleCI**: rejected because it doesn't show up named in this ecosystem's market
audit's direct evidence, and offers no clear advantage over GitHub Actions for a team
that already lives on GitHub.
- **Jenkins**: rejected, despite being the tool with the greatest presence in the surveyed
Spanish stack, for the cost of maintaining our own server given the team's current size,
and for having no direct equivalent to `act` for free local testing during the team's
learning stage.
What makes this ADR good, not just long
Notice three writing decisions in the document above, each deliberate:
The "Consequences" section includes real risks, not just benefits. An ADR that only lists advantages isn't an honest record of a decision — it's propaganda for the decision already made. The row about alignment with the Spanish job market is, on purpose, uncomfortable: the team chose a tool that shows up less frequently than the dominant alternative, and the ADR says so without softening it, with the exact same figure (5 of 13 versus ~3 of 13) you already know from this module's lessons 1 and 2.
The "What would change with Kubernetes" section doesn't decide a migration that isn't needed yet. It's tempting, when writing an ADR, to resolve every possible future all at once — this document resists that temptation on purpose: it documents its own scope's exact boundary (infrastructure CI/CD via Terraform, not application CI/CD via Kubernetes) and explicitly leaves it that, if that situation arrives, it deserves its own ADR, not an improvised clause inside this one.
"Alternatives considered and rejected" names why each one was rejected, with a specific reason. It doesn't say "we chose GitHub Actions because it's better" — it says, for each alternative, the specific reason it didn't fit the team's real constraints at this moment. That's what separates a useful ADR from a vague note: someone reading this document in six months, or on a different team, can assess whether those specific reasons still hold, without having to guess what was considered and what wasn't.
Common mistakes
Writing an ADR after the decision, as an empty retroactive justification (the most common one in real practice). What happens: a team makes a decision informally, and someone writes the ADR weeks later, no longer remembering precisely which alternatives were really considered. Why it happens: writing the ADR feels like a bureaucratic chore that comes after, not part of the deciding process. How to spot it: if your "Context" and "Alternatives considered" are vague, with no numbers or concrete sources. How to fix it: this lesson's ADR works because it cites specific evidence (5 of 13, VALIDACION.md's exact source) at the moment of the decision, not an approximate reconstruction afterward. If you ever write a real ADR, do it while the decision is fresh, with the sources at hand.
Confusing "Consequences" with "Benefits" (format-based, already flagged above). What happens: someone writes the consequences section listing only the positive, as if it were a list of arguments in favor of the decision already made. Why it happens: after defending a decision in "Decision," it's natural to keep going in defensive mode in the next section. How to spot it: if your "Consequences" section has no item an honest critic of the decision could point to as a real cost. How to fix it: Nygard is explicit about this — "all consequences should be listed here, not just the positive ones." An ADR with no acknowledged risks or costs isn't a decision record, it's marketing for the decision.
Believing an ADR replaces the code or the technical documentation (scope-based). What happens: someone thinks that, with the ADR written, there's no longer a need for a pipeline README.md (the one you're going to write in Module 8's capstone) or comments in the YAML itself. Why it happens: both documents talk about "why" the pipeline exists. How to spot it: if you look inside an ADR for "how to run act" or "what to do if apply.yml fails" instructions. How to fix it: an ADR documents why a decision was made, at a specific moment, with the information available then — it isn't an operations manual. Module 8's (capstone) pipeline README.md is the document that does fulfill that different role: practical instructions, not decision history.
Exercises
Exercise 1 — Identify the five sections without looking at the ADR. From memory, name the five sections of the standard ADR format, in the correct order, and what question each one answers.
See solution
1. Title — what decision is this, in a few words? 2. Status — is this decision proposed, accepted, deprecated, or superseded by another? 3. Context — what situation, with what constraints, led to needing this decision? 4. Decision — what got decided, exactly, in active voice? 5. Consequences — what results from having made this decision, the good and the bad alike?
Exercise 2 — Find Andes Cargo's ADR's most uncomfortable acknowledged risk. Without rereading the complete document, what's the risk this lesson's ADR explicitly acknowledges, that a less honest document might have omitted?
See solution
That choosing GitHub Actions leaves the team less aligned with the tool that shows up most in the surveyed Spanish market's job postings — Jenkins, in 5 of 13 postings versus ~3 of 13 for GitHub Actions. It's a real employability risk for the team itself, not just a minor technical limitation, and the ADR names it without softening it, with the exact same figure cited in this module's lessons 1 and 2, instead of omitting it or diluting it into a vague phrase like "there could be some misalignment with the market."
Exercise 3 — Write the title of Andes Cargo's next ADR. Per this ADR's "What would change if Andes Cargo had a Kubernetes cluster" section, what title and number would the next ADR the company would need to write have, if that situation ever came to pass?
See solution
ADR-0002: Choosing ArgoCD/Flux for application GitOps on EKS (the exact number might vary depending on how many ADRs exist by then, but this lesson's ADR already suggests the title and the central content: a pull-based GitOps decision, evaluated with the same market and scope honesty as this document, specifically for deploying an application with running replicas — not for the Terraform-declared infrastructure this ADR-0001 already covers).
Summary and next step
In this project you wrote Andes Cargo's complete ADR: the real document where the team justifies, in writing, its choice of GitHub Actions over Jenkins, GitLab CI, and CircleCI, with the exact market evidence cited (5 of 13 versus ~3 of 13), the decision's three concrete reasons, the acknowledged risks with no softening, and an honest section on what would change the day a Kubernetes cluster exists. You confirmed, by writing it yourself, what makes an ADR a useful decision record —real consequences, rejected alternatives with specific reasons, explicit scope— instead of an empty after-the-fact justification.
Before moving on you should be able to: name the ADR format's five sections and what each one answers; explain why a good ADR's "Consequences" section includes risks, not just benefits; and write, from memory, the title of the next ADR Andes Cargo would need if it adopted Kubernetes.
With this project, Module 7 is fully closed: you met three CI tools that solve the same problem with different syntax (lesson 2), two genuinely different GitOps mechanisms (lessons 3-4), an application deployment territory you never visited (lesson 5), the exact boundary between infrastructure and application CI/CD (lesson 6), ran it with your own hands (lesson 7), and turned it into a real decision document (this project). Module 8 —this entire guide's final capstone— picks Andes Cargo's complete technical pipeline back up, not this ADR, for an end-to-end walkthrough with a real change and a rejected one.
Resources
- Michael Nygard — Documenting Architecture Decisions — the original article that defined the five-section ADR format used in this lesson.
- npryce/adr-tools — GitHub — a command-line tool for managing ADRs, the source for the
doc/adr/folder convention with numbered files. src/paths/aws-cloud-ecosystem/VALIDACION.md(NIEVA, market audit, jul-2026) — the exact source for all the evidence cited in this lesson's ADR.- This guide's Module 7, lessons 2 through 4 — the ADR's technical content's direct source: tool comparison, ArgoCD/Flux, push vs. pull.