Also at Deasil Works · txn2 · Plexara
Profiles GitHub · X · LinkedIn
Theme Light · Auto · Dark
Professional notes by Craig Johnston
long-form, short-form, working drafts · since 2008
VOL. XIX · MMXXVI
127 NOTES IN PRINT
FOLIO CXXVII 2026-07-21 · 6 MIN · SHORT-FORM

GitOps: Git as the Source of Truth for Your Cluster

Argo CD and Flux, the deployment half the 2020 book left out, with manifests reconciled from a repo you own

Diagram · folio cxxvii
stateDiagram-v2
  [*] --> Synced: cluster matches git
  Synced --> Drift: live state diverges
  Synced --> OutOfDate: CI bumps image tag in git
  Drift --> Reconciling: controller (Argo CD / Flux)
  OutOfDate --> Reconciling: controller (Argo CD / Flux)
  Reconciling --> Synced: apply desired state

The 2020 book covered building software on the platform, and it stopped there. It said so directly: deployment was out of scope. That left the most important half of the loop unwritten, because building an image is only useful if something reliably gets it running on the cluster. The answer in 2026 is GitOps, where a git repository is the source of truth for what should be running and a controller continuously makes the cluster match it. This post covers it with the two projects that own the space, Argo CD and Flux, closing the loop the build pipeline opened.

This series rebuilds my 2020 Apress book, Advanced Platform Development with Kubernetes, for 2026. The approach behind it comes from building and running data platforms in production for more than twenty years.

§Git as the Source of Truth

Think about how every component in this series was deployed: a manifest applied with kubectl apply. That works, and it has a quiet problem. The cluster’s actual state is now whatever the last person to run kubectl made it, which may or may not match what is in your manifests, and nobody can be sure without checking. Configuration drifts, a hotfix applied by hand and never committed lingers, and rebuilding the cluster means remembering every apply anyone ever ran.

GitOps fixes this by making git the single source of truth. The desired state of the cluster, all those manifests, lives in a repository, and a controller running in the cluster continuously compares the repository to the live state and reconciles any difference. You do not kubectl apply to deploy; you commit and push, and the controller applies it. The model is pull, not push: the cluster pulls its desired state from git rather than someone pushing changes into it. The benefits fall out of that one idea. The repository is an audit log of every change, with review and history. Drift is detected and corrected, because the controller is always reconciling. Rebuilding the cluster, or standing up a second one, is pointing a controller at the repository. Every manifest you wrote becomes the declarative source the cluster reconciles to.

§Argo CD or Flux

Two CNCF graduated projects own this, and either is a sound choice. Argo CD is application-centric and comes with a rich web UI that shows every application, its sync status, and a live diff between git and the cluster, which makes it especially approachable and good for teams who want to see their deployments. It models what you deploy as Application resources. Flux is leaner and more composable, a set of controllers that do one thing each, with no built-in UI by default, favored by people who want GitOps as a minimal, scriptable foundation. Both pull from git, both reconcile continuously, both are mature and widely run. I will use Argo CD here for its visibility; the concepts transfer directly to Flux.

§Deploy Argo CD and an Application

Install Argo CD into the cluster, then tell it about a repository to reconcile.

kubectl create namespace argocd
kubectl apply -n argocd -f https://raw.githubusercontent.com/argoproj/argo-cd/stable/manifests/install.yaml

An Application points Argo CD at a path in a git repository, the Forgejo repo holding your platform manifests, and a destination in the cluster, and tells it to keep them in sync. With automated sync, self-heal, and prune turned on, Argo CD applies what git says, corrects anything that drifts, and removes anything deleted from git.

apiVersion: argoproj.io/v1alpha1
kind: Application
metadata:
  name: platform
  namespace: argocd
spec:
  source:
    repoURL: https://git.apk8s.dev/platform/manifests.git
    path: clusters/production
    targetRevision: main
  destination:
    server: https://kubernetes.default.svc
  syncPolicy:
    automated:
      selfHeal: true     # correct drift automatically
      prune: true        # delete what git no longer declares

From here, deploying a change is committing to that repository. Argo CD notices, shows the diff, and applies it. Someone editing the cluster by hand sees their change reverted on the next reconcile, because git, not their kubectl, is the truth.

§Closing the Loop: Image Updates

The build pipeline produces a new image tag; GitOps deploys what git declares; the missing link is getting the new tag into git. Argo CD Image Updater watches your registry for new tags matching a pattern and writes the updated tag back to the manifests repository, which Argo CD then deploys. Now the full loop is automatic and auditable: a developer pushes code, CI builds and signs an image, the image updater commits the new tag to git, and Argo CD reconciles it onto the cluster. Every step is recorded as a commit, and nothing reaches the cluster except through git. The deployment half the 2020 book left out is now in place, and it is more reliable than a set of remembered kubectl commands.

§Operating GitOps

Self-healing is the safety net. Because the controller reconciles continuously, the cluster repairs itself toward the declared state: a deleted resource comes back, a drifted config is corrected, an accidental change is undone. The cluster tends toward what git says, which is what you reviewed and approved.

Rollback is a git revert. A bad deploy is undone by reverting the commit, and the controller rolls the cluster back to the previous state. Your deployment history and your recovery procedure are the same thing, your git history, which is far less frightening than a hand-rolled rollback under pressure.

Scale with app-of-apps and sync waves. A single Argo CD application can manage other applications, the app-of-apps pattern, so the whole platform’s deployment is itself declared in git. Sync waves order the apply, so a database comes up before the thing that depends on it. The ordering and structure of the platform’s deployment become declarative too.

§When Something Is Wrong

An application will not sync. The repository or the manifests. Argo CD shows the error in its UI and status: a manifest that does not apply, a missing CRD, or a repo it cannot reach. The diff view points straight at what differs.

Argo CD cannot reach the repository. Credentials. A private Forgejo repo needs credentials configured in Argo CD; a repo it cannot read produces an application stuck unknown.

A change keeps reverting. That is self-heal working. Someone is editing the cluster by hand against what git declares, and the controller is correcting them. Make the change in git, not with kubectl.

The image updater does not bump the tag. The tag pattern or registry access. Confirm it can read the registry and that the tag matches the configured pattern; it only updates tags it is told to watch.

§What You Have

The deployment half of the platform, finally written: git as the single source of truth, a controller reconciling the cluster to it, automatic correction of drift, rollback by revert, and a fully closed loop from a code push through a signed build to a running deployment, every step audited in git. No more wondering whether the cluster matches the manifests, because it is continuously made to.

With the forge, the builds, and the deployments in place, the platform’s delivery layer is complete. The next posts go a level deeper into the cluster’s own foundation, starting with the container runtime underneath everything: containerd, and how to configure and debug it now that Docker is off the node.

← back to all notes