Git, CI/CD & GitOpsLXXII · GitOps FoundationsFoundations
Declarative desired state — describing what should be, not how to get there
What you'll learn
- Define a declarative desired state as a complete, self-contained description of "what should be"
- Distinguish declarative formats (YAML, HCL, Jsonnet, CUE) from imperative scripts
- Explain why a declarative format is what makes continuous reconciliation possible
- Recognise the failure modes of an incomplete or non-idempotent declaration
Prerequisites
Verified against Git 2.55.x teaching target; 2.40+ minimum · GitHub Actions continuous service; Aug 2026 documentation baseline · Argo CD v3.5.x teaching target; v3.0+ minimum · Flux v2.9.x · Sigstore Cosign v3.1.x · SLSA v1.2 · OCI Distribution Specification v1.1 · Git LFS v3.7.1 · Kubernetes (cross-course target) 1.36.x
A declarative description is a complete statement of what should be. An imperative script is a list of how to get there. The distinction is older than GitOps - the database world has had it since the 1970s - but it is the load-bearing idea of the GitOps model because every other principle depends on it.
Three properties follow from a declarative description:
- It is idempotent. Applying the same description twice produces the same end state as applying it once.
- It is diffable. Two descriptions can be compared textually and the delta is meaningful.
- It is convergent. A loop can observe the current state, compute a diff, and apply until the diff is empty.
flowchart LR
A["Imperative script"] -->|"run once"| B["System state"]
C["Declarative description"] -->|"diff against observed"| D["Reconciliation loop"]
D -->|"apply"| B
D -->|"observe"| B
The left branch is what CI/CD pipelines look like without a loop. The right branch is what GitOps requires. The same description can power either branch, but only the right branch produces continuous convergence.
The four formats that matter
GitOps does not pick a single declarative format. The four that matter in infrastructure work, ordered roughly by adoption:
- YAML. The Kubernetes lingua franca. Readable, diff-friendly, weak typing. Powers raw Kubernetes manifests, Helm values, Kustomize overlays, Argo CD Applications, Flux Kustomizations.
- HCL. HashiCorp Configuration Language. Typed, block-structured, supports variables and modules. Powers Terraform, Nomad, Vault policies.
- Jsonnet. A data-templating language with first-class inheritance and immutability. Powers the heavier Kubernetes manifests in shops that want compile-time safety without HCL’s surface area.
- CUE. A data-constraint language with logical unification. Validates that data conforms to a schema before the data is applied. Used where multiple environments must be the same shape.
The choice between them is not a moral question. YAML is the default because Kubernetes picked it; HCL is the default where Terraform runs; Jsonnet and CUE appear where the YAML duplication becomes painful.
What “complete” means
A declarative description is complete if applying it once to an empty system produces the intended end state. Completeness is the property that makes the loop work: the controller does not need to remember anything about the system - it only needs to read the description and converge.
In practice, completeness is broken by:
- Out-of-band state. Things that exist in the cluster but not in the description (a manually-created Secret, a leftover PVC). The controller either ignores them (drift) or deletes them (data loss). Either is a failure mode.
- Implicit defaults. A description that omits a field the API fills in with a default is no longer self-contained. The description does not equal the desired state; the description plus the API defaults equals the desired state.
- Side effects. A description whose application triggers an external write (a webhook, a CI run, a Slack message) is not really declarative - the side effect is an imperative step hidden in the declaration.
How a controller reads a description
A controller has three jobs on each reconciliation tick:
- Read the desired state from the source of truth (Git, usually; an OCI artifact in some pipelines).
- Read the observed state by querying the cluster API.
- Compute the delta and apply it.
The diff between the two states is the work the controller has to do. An empty diff is a healthy cluster; a non-empty diff is a controller with work to do. The diff is also the audit signal: “what does the cluster say it is, and what does Git say it should be?”.
argocd app list
This is the Argo CD CLI command that lists all Applications the controller is reconciling and the diff between their desired state and their observed state.
Production discipline
- The description is the contract. Treat the manifest set as a schema, not as a suggestion. Lint it, validate it, and reject changes that do not converge.
- Out-of-band writes are bugs. A production change made by
kubectl editinstead of a Git commit is a bug in the change-management process, not in the engineer. - The apply is auditable, the diff is the receipt. Every reconciliation tick should be observable; every applied delta should be traceable to a commit.
Cross-course references
- Ansible for Production Sysadmins - Parts XXXVII-XXXIX
cover the move from imperative
command:modules to declarative resources; the same convergence property applies. - Terraform for Production Sysadmins - Parts IX-XII (State) cover the declarative resource model that Terraform pioneered for infrastructure.
- Kubernetes for Production Sysadmins - The controllers pattern relies on the same declarative-plus-reconcile model.
Quiz
Knowledge check · 1 question
Q1. Which property is required for a declarative description to make a continuous reconciliation loop possible?
Passing score: 75%. Answers are checked in this browser.