Right, let’s get this straight. You’re about to learn the one principle that makes GitOps more than just a fancy buzzword. It’s the hill the entire methodology dies on, and if you don’t internalize this, the rest of this chapter is just you learning a fancy new tool to shoot yourself in the foot with. It’s this: Git is the single source of truth.

Not a source. The source. The canonical, unassailable, definitive record of what your system should look like. Your Kubernetes cluster? That’s just a runtime instance, a mere mortal reflection of the divine blueprint stored in Git. It’s transient, potentially flawed, and ultimately subservient. If the cluster gets drunk and decides to deviate from the Git manifest, Git wins. Every. Single. Time. This isn’t a suggestion; it’s the core mechanism.

Why This Isn’t Just Version Control on Steroids

Sure, you’ve been using Git for code for years. “But my infrastructure is code too!” you say. I know. This feels different because it is. This isn’t just about tracking changes; it’s about enforcing state. In traditional CI/CD, a pipeline script is the boss. It says “run these commands, deploy this artifact.” The script itself is the source of truth. In GitOps, that script is fired. Its job is done once it merges a change into Git. The Git repository is the deployment instruction. ArgoCD wakes up, sees the new commit, and says, “Right then, the humans have decided the system should look like this now. My job is to make reality match that desire.” The pipeline pushes to Git; ArgoCD pulls from it. This pull-based model is a seismic shift in responsibility and security.

The Absolute Tyranny of the Main Branch

This means your main branch (or whatever you’ve deemed your “production” branch) is sacrosanct. It is law. The state of that branch defines the desired state of your cluster. There is no “oh, I’ll just run kubectl apply -f this one time.” Do that and you’ve introduced a Schrödinger’s configuration: is the cluster’s state defined by what’s in Git or by your ad-hoc command? The answer must always, unequivocally be Git.

This is where the first major pitfall awaits. You kubectl edit a deployment to quickly change an environment variable to debug something. You’ve now created configuration drift. The live cluster has diverged from Git. ArgoCD will eventually wake up, see this heresy, and revert your change. Poof. Your “fix” is gone. This is a feature, not a bug. It forces all changes to be intentional, peer-reviewed, and recorded. The best practice? If you need to debug, use kubectl patch with --dry-run=client or, better yet, make the change in a feature branch and see the PR through. Embrace the tyranny. It protects you from yourself.

What “Truth” Actually Looks Like in a Repo

This isn’t just dumping a bunch of YAML files in a directory called k8s/. The structure of your Git repository is how you encode your desired state. A common, excellent pattern is to have a directory for each cluster or environment.

my-app-infrastructure/
├── apps/
│   ├── my-app/
│   │   ├── base/
│   │   │   ├── deployment.yaml
│   │   │   ├── service.yaml
│   │   │   └── kustomization.yaml
│   │   └── overlays/
│   │       ├── production/
│   │       │   ├── replica-count-patch.yaml
│   │       │   └── kustomization.yaml
│   │       └── staging/
│   │           ├── configmap-patch.yaml
│   │           └── kustomization.yaml
├── cluster-config/
│   └── production/
│       └── argocd-project.yaml
└── infrastructure/
    └── production/
        ├── redis/
        └── postgresql/

Here, the “truth” for the production version of my-app is defined by the kustomization.yaml in apps/my-app/overlays/production/. It might patch the replica count from the base definition. This declarative approach is what ArgoCD expects. It doesn’t run scripts; it runs kubectl apply against the rendered YAML from this defined source.

The Reconciliation Loop: The Engine of Truth

ArgoCD isn’t just a dumb sync tool. It runs a continuous reconciliation loop. It’s basically a control freak with a stopwatch. It constantly compares the live state in the cluster with the desired state in Git. If they differ, it takes action to converge them. You can configure how often it checks Git (the pull frequency), but its entire raison d’être is to eliminate drift.

This is also where you handle the edge case of “bad” commits. What if someone merges a manifest with a syntax error? A well-configured ArgoCD Application will go into a Degraded or Unknown state. It will try to apply the change, fail, and loudly tell you it failed. The key is that it won’t silently ignore the failure or leave the cluster in a half-baked state. The truth is now “a broken state,” and it’s honest about it. This is infinitely better than a silent failure from a custom deployment script. You roll back the Git commit, and ArgoCD will happily roll the cluster back with it. The entire history of your system’s state, successes and failures, is now your Git history. It’s glorious.

So remember: your cluster is a pet, but your Git repo is the owner’s manual. You don’t guess what the pet needs; you read the manual and act accordingly. Any other approach is just chaos disguised as flexibility.