Skip to main content

GitOps: pull-based reconciliation with Argo CD & Flux

You've met the reconciliation loop three times now — Terraform (Chapter 3), Kubernetes deployments, and the control plane (Chapter 4). GitOps applies that same loop to deployment itself. The idea is deceptively simple: the desired state of your whole system lives in a Git repository, and an agent continuously makes the live cluster match it. This lesson nails the GitOps model, the difference between push-based and pull-based deployment (and why pull is safer), and how Argo CD and Flux implement it — including the part most guides skip: drift detection and correction.

The core GitOps model

GitOps rests on four principles. Hold all four together — dropping any one is how teams get GitOps wrong:

Git repo\n(desiredstate,\ndeclarative)In-clusteragent\n(reconciler)Livecluster\n(actualstate)observeact to close gap
  1. Git is the single source of truth. The entire desired state of the system — every manifest, every config overlay, every image digest — is described in a Git repo. If it's not in Git, it shouldn't be running.
  2. Desired state is declarative. You describe what you want (10 replicas of my-app@sha256:…), not the imperative steps to get there. (Same declarative model as IaC and Kubernetes.)
  3. Pull-based reconciliation. An agent running inside the cluster continuously pulls the desired state from Git and reconciles the live cluster to match — read desired, observe actual, act to close the gap, repeat. The loop you already know.
  4. Continuous drift detection and correction. The agent doesn't just deploy once; it forever watches for drift — any divergence between Git and the live cluster — and corrects it.

The headline payoff: deploying = committing to Git, and rolling back = reverting a Git commit. Your release history is your Git history — auditable, reviewable, and revertible with the tools you already use.

Push vs pull: where does the deploy come from?

This is the distinction that makes GitOps GitOps. There are two ways an artifact can reach a cluster:

  • Push-based CD (traditional). The CI/CD pipeline reaches out and pushes changes into the cluster — it runs kubectl apply or helm upgrade against the production cluster. For this to work, the pipeline must hold credentials to your production cluster.
  • Pull-based GitOps. An agent inside the cluster pulls the desired state from Git and applies it itself. The CI pipeline never touches the cluster — it only updates Git. No external system holds cluster credentials.
CI pipeline\n(holdscluster creds!)ClusterCI pipelineGitIn-cluster agentClusterkubectl applycommitpullapply itself

Why pull is safer — the credential argument is the heart of it:

  • No cluster credentials in CI. In the push model, your CI system is a high-value target: anyone who compromises it gets production cluster access. In the pull model, CI only has write access to a Git repo — never to the cluster. The blast radius of a compromised pipeline shrinks dramatically.
  • The cluster pulls, so it controls its own changes. The agent inside the cluster decides when and what to apply, and can refuse anything that doesn't match Git.
  • Drift is corrected automatically (next section) — push-based deploys are fire-and-forget and don't watch for divergence afterward.

The part guides skip: drift detection and correction

A common shallow take is "GitOps is just running Argo CD." It is not. The defining behavior is the continuous reconciliation loop and what it does about drift.

Drift is any difference between what Git says should be running and what's actually running. It happens constantly: someone runs kubectl edit to "quickly fix" production at 2 a.m., a one-off kubectl scale bumps replicas, a controller mutates something. In a push-based world that manual change just sticks — and now production silently differs from your source of truth, undocumented and unreproducible.

A GitOps agent handles this directly. Because it reconciles continuously, it notices the manual change (the live cluster no longer matches Git) and then, depending on configuration:

  • Self-heal mode — it reverts the manual change automatically, snapping the cluster back to match Git. Your 2 a.m. kubectl edit is undone within minutes, because Git is the source of truth and manual changes are by definition unauthorized drift.
  • Detect-and-alert mode — it flags the divergence as "OutOfSync" and surfaces it for a human, without auto-reverting.
Git: replicas = 3Agent reconcileskubectl scale →8\n(manual drift)Live: replicas = 8revert to3\n(self-heal)Live: replicas = 3againdetects mismatch

This is the real lesson: what happens on a manual cluster change is the whole point of GitOps. Either it's reverted (self-heal) or it's loudly flagged (drift detection) — never silently tolerated. The cluster can no longer drift away from its documented, reviewed state and stay that way. (This is the exact drift-elimination promise from Terraform in Chapter 3, now enforced continuously rather than only when you next run apply.)

The two controllers: Argo CD and Flux

Two open-source GitOps controllers dominate Kubernetes; both implement the model above:

  • Argo CD — Application-centric with a rich web UI that visualizes desired vs live state and shows exactly what's out of sync. Popular when teams want strong visibility and a clear sync/diff view. (Its sibling Argo Rollouts adds progressive delivery — lesson 5.6.)
  • Flux CD — A set of composable controllers, more Git-native and CLI/GitOps-toolkit oriented, often favored for a leaner, building-block approach. (Its companion Flagger adds progressive delivery — lesson 5.6.)

Both watch a Git repo, both reconcile the cluster to match, both detect and (optionally) correct drift. Choosing between them is a matter of UI preference and ecosystem fit, not a difference in the underlying model.

:::note Spinnaker and Harness: enterprise deployment orchestrators Argo CD and Flux are Kubernetes-native, Git-driven reconcilers. Two other names you'll meet sit a level up, as broader CD / deployment orchestrators aimed at large enterprises with many environments, clouds, and approval gates:

  • Spinnaker — the open-source, multi-cloud continuous-delivery platform originally from Netflix. It orchestrates complex deployment pipelines across AWS/GCP/Azure/Kubernetes with built-in deployment strategies and automated canary analysis. It predates GitOps and is pipeline-centric (it pushes deployments) rather than a pull-based reconciler — reach for it when you need heavyweight, multi-cloud release orchestration with manual gates and rich approval flows.
  • Harness — a commercial CD platform that wraps deployment, feature flags, and AI-assisted canary/verification in a managed product, so you buy the orchestration rather than assemble it. It's the build-vs-buy counterpart to running Argo/Spinnaker yourself.

The rule of thumb: Argo CD / Flux for Kubernetes-native GitOps reconciliation; Spinnaker / Harness when the org needs enterprise-scale, multi-cloud release orchestration with elaborate gating on top. :::

:::tip Durable vs dated The GitOps model — Git as source of truth, declarative desired state, pull-based reconciliation, continuous drift detection/correction — is durable; it's the reconciliation loop you already know, applied to deployment. The controllers (Argo CD, Flux) and their feature names are dated. If a tool pulls declarative desired state from version control and continuously reconciles the live system to it, it's doing GitOps, whatever it's called. :::

Common pitfalls

  • "GitOps = just running Argo CD." The model is the reconciliation loop and drift handling, not a tool install. Without continuous reconciliation and a drift policy, you don't have GitOps.
  • Ignoring what happens on manual changes. If kubectl edit to prod silently sticks, you've lost the source-of-truth guarantee. Decide explicitly: self-heal or detect-and-alert.
  • Putting deploy credentials in CI (push model). Long-lived cluster credentials in your pipeline are a huge attack surface. Pull-based agents keep cluster creds out of CI entirely.
  • Out-of-band changes as a habit. GitOps only works if Git is actually the source of truth. Routine "quick fixes" straight to the cluster undermine the whole model — make the change in Git.
  • Secrets in plaintext in Git. Git-as-source-of-truth includes config — but never raw secrets. Use sealed/external secrets (lesson 5.7) so secrets can live in Git references safely.

Why it matters

GitOps applies the reconciliation loop to deployment: Git holds the declarative desired state of the whole system, an agent inside the cluster continuously pulls that state and reconciles the live cluster to match, and it detects and corrects drift forever — so "deploy" means commit to Git and "roll back" means revert a commit. Pull-based GitOps is safer than push-based CD for one decisive reason: the cluster pulls its own changes, so no cluster credentials ever live in CI — shrinking the blast radius of a compromised pipeline. And the part most explanations miss is the drift behavior: a manual kubectl change to production is either auto-reverted (self-heal) or loudly flagged — never silently tolerated. Argo CD and Flux are the two controllers that implement all this. With safe, declarative deploys in place, the last risk to manage is the rollout itself — shipping a new version without breaking users.

Next: Progressive delivery & rollback →