A commit in an application repository eventually produces a binary somebody runs.
A commit in an infrastructure repository can delete a database.
That difference is the whole subject of this pillar. Everything Git does for application code — history, review, attribution, revert — applies to infrastructure code too. What changes is the consequence of getting it wrong, and therefore what the workflow around the commit has to do before the change reaches anything real.
Start with Infrastructure as Code + GitThe control layer
Section titled “The control layer”Infrastructure work has a shape, and Git sits in the middle of it.
A vertical chain: an engineer expresses intent; it becomes a commit in a Git repository; on a branch; opened as a pull request; validated automatically; reviewed by a human; merged; picked up by automation or a reconciliation controller; and applied to infrastructure.
Read that chain twice: once as a delivery pipeline, and once as an audit trail. Both readings are correct, and the second is the one infrastructure teams undervalue. Six months after an outage, the question “why is this security group open” has an answer only if the change went through this path.
Application code versus infrastructure code
Section titled “Application code versus infrastructure code”Both live in Git. They do not carry the same risk.
| Application code | Infrastructure code | |
|---|---|---|
| What a commit describes | Behaviour of a program | Configuration of real systems |
| What a bad merge costs | A broken build, a bad deploy | Deleted resources, exposed networks |
| Rollback | Redeploy the previous artifact | Sometimes; often not — see below |
| Blast radius | The service | Anything sharing the network, account or cluster |
| Reviewer needs | To read the diff | To read the diff and its operational effect |
| Test coverage | Usually meaningful | Usually partial at best |
The row that reshapes everything is the last but one. Reverting a Git commit does not reliably undo an infrastructure change. A commit that deleted a database can be reverted; the data does not come back. This is covered directly in drift and reconciliation and again in infrastructure promotion, because it is the single assumption that most often turns a small incident into a large one.
What this pillar covers
Section titled “What this pillar covers”Five clusters, each answering a different question about the same problem.
Infrastructure as Code
Terraform and OpenTofu as repositories: structure, review, state and environments.
Containers
From a commit to an image digest, and the identity that connects the two.
Kubernetes & GitOps
Declarative desired state, continuous reconciliation, Argo CD, Flux and promotion.
Ansible
Playbooks, roles and inventories under review, tested without touching production.
DevOps Repositories
Repository architecture, ownership, promotion, policy and platform engineering.
Infrastructure as Code + Git — Terraform and OpenTofu as repositories rather than as languages. How the code is laid out, how modules are versioned, what pull request validation should actually run, why state is not a source file, and how environments relate to directories, workspaces and branches.
Containers + Git — the path from a commit to a running image, and the identity that connects them. Dockerfiles under review, tagging strategy, the difference between a Git SHA and an image digest, registry workflow, and reproducible release builds.
Kubernetes + GitOps — declarative desired state and the controllers that continuously reconcile toward it. Repository structure, Kustomize and Helm, Argo CD and Flux, environment promotion, secret handling, and what drift actually is.
Ansible + Git — playbooks, roles, inventories and collections under version control, validated in CI without ever touching a production host, with Vault handled as the key-management problem it is.
DevOps Repository Engineering — the architectural decisions that cut across all four: monorepo or polyrepo, environment repositories, promotion through pull requests, CODEOWNERS, policy as code, drift detection, and Git as the interface a platform team offers its developers.
Git as source of truth — precisely
Section titled “Git as source of truth — precisely”“Git is the source of truth” is repeated so often that it has stopped meaning anything specific. It is worth saying what is and is not in the repository, because conflating these is the root of several expensive mistakes.
| Thing | Where it actually lives |
|---|---|
| Desired configuration | Git |
| Terraform/OpenTofu state | A remote backend — not Git |
| Kubernetes live resources | The cluster’s API server and etcd |
| Secrets | A secret manager, or encrypted with keys held elsewhere |
| Built artifacts | A registry, addressed by digest |
| Runtime data | Databases, queues, object stores |
| Everything a cloud provider knows | The provider’s own control plane |
Git holds declared intent. It does not hold the world. A GitOps controller’s job is precisely to notice the gap between the two and close it, which only makes sense as a concept if you accept that the gap exists.
That distinction has a practical consequence worth stating early: a repository can be entirely correct while the infrastructure is entirely wrong. Detecting that is drift detection, and it is a separate discipline from writing good configuration.
Four things people mean by “GitOps”
Section titled “Four things people mean by “GitOps””The term has been stretched to cover any deployment that involves a repository. This pillar uses it precisely, following the OpenGitOps principles, and separates it from three adjacent ideas.
Infrastructure as Code — infrastructure expressed in machine-readable files. Says nothing about how those files are stored, reviewed or applied.
Git-based workflow — those files live in Git and change through pull requests. This is most of what good infrastructure teams do, and it is not yet GitOps.
CI/CD for infrastructure — a pipeline runs on merge and pushes changes outward. A push model, with credentials held by the pipeline.
GitOps — desired state is declarative and versioned, and software agents continuously pull it and reconcile the system toward it. The pull and the continuous reconciliation are the distinguishing parts, and a workflow without them is not GitOps regardless of how much YAML is in the repository.
GitOps explained works through the four principles properly. The short version: Terraform in Git with a GitHub Actions pipeline is Infrastructure as Code with CI/CD. It is a good architecture. It is not GitOps, and calling it GitOps makes it harder to reason about what reconciliation would add.
Push and pull
Section titled “Push and pull”Two delivery architectures, both legitimate, with different trust boundaries.
Push — the pipeline holds credentials and reaches into the target.
GitHub Actions → credentials → cluster / cloud APIPull — an agent inside the target reads the repository and converges.
Git repository → agent inside the cluster → cluster API ↑ continuous reconciliationThe security difference is where the credential sits. In a push model, an external system holds standing access inward — mitigated substantially by OIDC and short-lived credentials, which is why that lesson matters here. In a pull model, the target holds read access outward and nothing outside needs write access to the cluster.
Neither is universally correct. Push handles ordered, imperative operations that reconciliation models handle badly — database migrations, for one. Pull handles continuous convergence and self-healing that push models cannot express. Most mature platforms run both, and are deliberate about which changes go through which.
Where infrastructure state lives
Section titled “Where infrastructure state lives”Four things get called “state” in DevOps conversations, and they are genuinely different objects. Keeping them separate is a prerequisite for most of the reasoning in this pillar.
The Git repository holds version-controlled desired configuration. Text, reviewed, with history.
Terraform or OpenTofu state is a record of what the tool believes it manages — a mapping between the resources in your configuration and the real objects in a provider, plus enough metadata to plan the next change. It contains resource attributes, and those frequently include sensitive values. It belongs in a remote backend with locking, and not in Git.
Kubernetes live state is whatever the API server currently reports. It changes without anybody committing anything — a pod is rescheduled, a controller writes a status, an autoscaler adds a replica.
GitOps desired state is the declarative configuration a reconciler is watching. It is usually a subset of what is in the repository — the part a particular controller has been pointed at.
Conflating any two of these produces a specific, recognisable mistake. Treating Terraform state as a source file leads to committing it. Treating Kubernetes live state as the source of truth leads to changes that vanish at the next reconcile. Treating the whole repository as the desired state leads to controllers fighting each other over the same resources.
Environment promotion
Section titled “Environment promotion”The other idea that runs through every cluster.
Promotion moves an approved version or desired-state change toward production through a controlled workflow — it does not independently rebuild something different for each environment.
In practice that means a specific, identifiable thing moves:
| Domain | What gets promoted |
|---|---|
| Containers | An image digest — not a tag, and not a rebuild |
| Kubernetes | A commit in the environment’s desired state |
| Helm | A chart version, with environment-specific values |
| Terraform modules | A module version reference |
| Ansible | A collection or role version |
The failure this rules out is rebuilding per environment. If staging builds its own image from main and production builds another one later, the artifact you tested is not the artifact you shipped, and every hour spent testing staging bought less than it appeared to.
The analogy has a limit worth naming now, because a whole cluster depends on it: a Terraform plan is not an artifact you can promote. A plan is computed against the state and the real world at a moment in time, and it is stale as soon as anything changes. Container images promote cleanly; infrastructure plans do not. Terraform environments works through what promotion means when the artifact model does not apply.
Safety is a first-class topic here
Section titled “Safety is a first-class topic here”Every cluster in this pillar carries safety material, because the commands involved genuinely destroy things.
Plans and diffs before actions. terraform plan, kubectl diff, helm template, --check mode. Read the effect before authorising it.
Disposable environments for exercises. Every exercise in this pillar uses a throwaway repository, a local cluster or a scratch project. No exercise requires production credentials, and none should be run against anything you would mind losing.
Least privilege for automation. Scoped workflow permissions, short-lived cloud credentials, agent identities that cannot escalate. The security pillar does the depth; this pillar applies it.
Destructive commands get warnings. terraform destroy, kubectl delete, Argo CD pruning, Flux reconciliation against the wrong cluster, Ansible against a real inventory. Where those appear, so does what they remove and how to check first.
What each tool actually is
Section titled “What each tool actually is”A short orientation, because the ecosystem’s naming is not self-explanatory and several of these are routinely confused with each other.
Terraform and OpenTofu are provisioning tools. They read declarative configuration, compare it against recorded state, and create, change or destroy provider resources. OpenTofu forked from Terraform and remains broadly configuration-compatible while having added features of its own — covered honestly, without picking a side.
Docker builds and runs containers. In a Git context what matters is that a Dockerfile is source, an image is an artifact, and the two are connected only by whatever identity you deliberately record.
Kubernetes is a control plane that continuously drives cluster state toward the objects declared in its API. It is worth noticing that Kubernetes is itself a reconciliation engine — GitOps extends that loop outward to include the repository.
Kustomize composes Kubernetes manifests by overlaying patches onto a base. No templating language; the output is diffable YAML.
Helm packages Kubernetes manifests as versioned, templated charts with values. Charts are artifacts that can live in an OCI registry alongside images.
Argo CD and Flux are GitOps controllers. They run inside a cluster, watch a source of desired state, and reconcile continuously. They solve the same problem with different operating models — compared without a verdict.
Ansible is configuration management: it connects to hosts and makes them match a described state, usually imperatively ordered and usually push-based.
The tools are not interchangeable and mostly do not compete. A realistic platform uses several — Terraform for cloud resources, a GitOps controller for cluster workloads, Ansible for host configuration — and the interesting engineering is at the seams between them.
What you will be able to decide
Section titled “What you will be able to decide”Not “what commands to type” — those are in the vendors’ documentation and change. The questions this pillar leaves you able to answer are architectural:
- Should this infrastructure live in one repository or several, and where do the ownership boundaries go?
- Directory per environment, workspace per environment, or repository per environment — and why not branch per environment?
- Which module versions may production consume, and how does an upgrade get reviewed?
- What must a Terraform pull request show a reviewer before anybody can responsibly approve it?
- Where does state live, who can read it, and what happens if it is lost?
- Does a container tag or a digest belong in the manifest that reaches production?
- Is a Git SHA sufficient to identify what is running? (It is not, and knowing why matters.)
- Push pipeline or pull reconciliation for this particular change?
- Argo CD or Flux, for this team’s operating model?
- How do secrets reach a cluster whose desired state is public in a repository?
- When something drifts, do you detect it, alert on it, or automatically correct it?
- What does a platform team actually offer developers, expressed as repositories and pull requests?
The infrastructure pull request
Section titled “The infrastructure pull request”One artefact deserves naming on the hub, because it is where most of this pillar’s practice concentrates.
An application pull request asks: is this code correct? An infrastructure pull request asks a harder question, because the diff and the effect are different objects. A three-line change to a Terraform variable can produce a plan that replaces a database. A one-word change to a Kubernetes manifest can restart every pod in a namespace. Reading the diff tells you almost nothing about that.
So a good infrastructure pull request puts the effect in front of the reviewer:
- What changes, in source terms — the diff itself
- Why, linked to the issue, incident or requirement that prompted it
- Which environments are affected, and in what order they will be
- What the plan or diff says — resources created, changed, and above all destroyed
- Security impact — anything touching networks, identity, permissions or secrets
- The recovery path if it goes wrong, stated before it does
- Dependencies — what must happen before or after, and whether anything is irreversible
Most of that is mechanical and belongs in automation: pull request validation covers producing it. The part that is not mechanical is the reviewer forming a view about whether the operational effect is acceptable — which is why CODEOWNERS for infrastructure matters more here than in an application repository. The reviewer needs to be somebody who would recognise a bad plan.
How to work through it
Section titled “How to work through it”The clusters are ordered deliberately and build on each other, but they are usable independently if you already work in one of these areas.
Start at Infrastructure as Code if you write Terraform or OpenTofu. It establishes the repository patterns the rest of the pillar reuses.
Start at Containers if your immediate problem is build and release identity — what is running, and which commit produced it.
Start at Kubernetes + GitOps if you are choosing or operating a reconciliation tool. It is the largest cluster and the one with the most load-bearing concepts.
Start at Ansible if your automation is configuration management rather than provisioning.
Start at DevOps Repository Engineering if the question in front of you is architectural rather than tool-specific — and come back to it regardless, because it is where the pillar’s threads are tied together.
The principle underneath
Section titled “The principle underneath”Git is not merely where infrastructure code is stored. It is where infrastructure change becomes reviewable, auditable, automatable and — through GitOps — continuously reconcilable.
Everything in this pillar is an application of that sentence. Repository structure exists to make change reviewable. Pull request validation exists to make the effect visible before the decision. Module and image versioning exist to make promotion mean something. Reconciliation exists so that the declared state and the real state cannot silently diverge.
A team that has only put its infrastructure files under version control has done the first and easiest part. The rest of this pillar is the remaining work.
Begin: Infrastructure as Code + GitContinue to Git at Scale & Enterprise Engineering
Section titled “Continue to Git at Scale & Enterprise Engineering”This pillar treats infrastructure as the thing being engineered through Git. Git at Scale & Enterprise Engineering changes the variable that everything else in the Academy has held constant: size.
The platform engineering practices here assume a repository you can clone in a minute and an organisation whose teams you can name. Pillar 8 asks what happens when neither holds — when a repository is twenty gigabytes, when there are two thousand of them across twenty organisations, when identity is provisioned from a corporate directory, when a policy change has to reach every repository at once, and when somebody has to prove what was enforced and when.
It covers large repository engineering, enterprise governance and rulesets, identity and provisioning, audit, Actions governance and compliance evidence, and repository fleet engineering and migration.
Continue: Git at Scale & Enterprise Engineering