⚡ TL;DR: GitOps makes Git the single source of truth and lets a controller reconcile reality to match it, so rollback is a git revert, every change is audited, and drift heals itself. Includes ArgoCD vs Flux and the trade-offs.
Contents
- Who pushed that change to production?
- The one-sentence definition
- The reconciliation loop
- Pull vs push: why the direction matters
- ArgoCD vs Flux
- A real ArgoCD Application
- What GitOps actually buys you
- Common mistakes that cost hours
- Takeaways
- Where to go next
Who pushed that change to production?
Someone ran a kubectl apply at 11pm to fix an incident. Nobody wrote it down. Three weeks later the cluster behaves differently than the YAML in your repo says it should, and no one can explain why. This is configuration drift, and it's the slow rot at the heart of imperative deployment, every kubectl edit, every manual scale, every quick fix moves the live system away from any written record of it.
GitOps fixes this by inverting the flow. Instead of pushing changes to the cluster, you commit the desired state to Git and let a controller pull it, continuously reconciling the live cluster to match. Git becomes the single source of truth, your deploy history becomes your git log, and rollback becomes git revert. This article builds that model and shows it with a real ArgoCD Application.
📌 Who this is for: Engineers running things on Kubernetes who deploy with scripts or CI that runs kubectl, and want auditable, self-healing delivery. Comfort with Git and basic k8s manifests is assumed; we explain the GitOps parts from scratch.
The one-sentence definition
GitOps is operating your infrastructure by declaring its desired state in Git and letting an automated controller continuously make reality match that declaration.
Two words carry the weight. Declarative: you describe the end state you want (10 replicas of v2), not the steps to get there. Reconciliation: a controller constantly compares desired (Git) to actual (cluster) and closes the gap, forever. It's a thermostat, not a light switch.
| In the real world | In tech |
|---|---|
| 🌡️ You set the target temperature | Commit desired state to Git |
| 🔁 The thermostat reads the room, constantly | Controller diffs Git vs cluster |
| 🔥 It heats or cools to close the gap | Reconcile: apply changes to match Git |
| 🪟 Someone opens a window (drift) | Manual kubectl edit |
| ♨️ It corrects automatically | Self-heal back to Git's state |
GitOps is a thermostat for your cluster.
The reconciliation loop
The GitOps loop. Developers never touch the cluster directly, they open a pull request against the config repo. The controller (ArgoCD or Flux) lives inside the cluster, watches Git for the desired state and the cluster for the actual state, and reconciles any difference. The dashed arrows are the continuous watch; the solid arrow is the apply that closes drift.
- A change starts as a pull request: You don't run kubectl. You edit a manifest in the config repo and open a PR, the same review, approval, and audit trail as any code change.
- The merge is the deploy: When the PR merges to the main branch, Git's desired state has changed. That's the only action a human takes.
- The controller notices the diff: Living inside the cluster, the controller polls or is webhook-notified that Git now differs from the running cluster.
- It reconciles: The controller applies whatever is needed to make the cluster match Git, creating, updating, or deleting resources.
- It keeps watching, forever: If anyone hand-edits the cluster, the controller sees the drift and pulls it back to Git's truth (self-heal). The repo always wins.
Pull vs push: why the direction matters
Traditional CI/CD pushes: your pipeline holds cluster credentials and runs kubectl apply from the outside. GitOps pulls: a controller inside the cluster reaches out to Git and applies changes itself. That flip sounds small but it changes your security posture and your guarantees.
| Push (CI runs kubectl) | Pull (GitOps controller) | |
|---|---|---|
| Who applies changes | External CI pipeline | Controller inside the cluster |
| Cluster credentials live | In CI (broad blast radius) | Inside the cluster only |
| Drift detection | None, fire and forget | Continuous; self-heals |
| Source of truth | Whatever last ran | Git, always |
| Rollback | Re-run an old pipeline | git revert |
The pull model keeps cluster credentials inside the cluster and makes drift detectable.
💡 Tip: The credentials point is underrated. With push, every CI runner needs cluster-admin-ish access, so a compromised pipeline is a compromised cluster. With pull, the cluster credentials never leave the cluster, CI only ever needs write access to a Git repo.
ArgoCD vs Flux
The two dominant controllers do the same job with different personalities. ArgoCD ships a polished web UI and an explicit Application resource, great for teams who want to see sync status and drift. Flux is leaner, more composable, and Git-native to a fault, great for teams who want everything, including Flux's own config, to live in YAML with no dashboard.
| ArgoCD | Flux | |
|---|---|---|
| UI | Rich web dashboard | CLI-first, minimal UI |
| Core unit | Application CRD | Kustomization + sources |
| Multi-tenancy | Projects, RBAC, SSO built in | Via namespaces + RBAC |
| Image automation | Add-on | Built-in image updater |
| Feels like | A deploy console | A set of Unix tools |
Both are CNCF graduated and production-proven. Pick on UI preference and how composable you want it.
A real ArgoCD Application
Here's the resource that wires it all together. This Application tells ArgoCD: watch this repo path, keep this namespace in sync with it, and heal any drift automatically. Once this exists, you never deploy this app by hand again, you change the repo.
application.yaml
apiVersion: argoproj.io/v1alpha1
kind: Application
metadata:
name: web
namespace: argocd
spec:
project: default
source:
repoURL: https://github.com/acme/cluster-config.git
targetRevision: main # the branch that is 'production'
path: apps/web/overlays/prod # where this app's manifests live
destination:
server: https://kubernetes.default.svc
namespace: web
syncPolicy:
automated:
prune: true # delete resources removed from Git
selfHeal: true # revert manual cluster edits back to Git
syncOptions:
- CreateNamespace=true
Read syncPolicy carefully, it's where GitOps gets its teeth. prune: true means deleting a manifest from Git deletes it from the cluster (Git is complete truth, not just additive). selfHeal: true means a hand-edit to the live cluster gets reverted on the next reconcile. Together they guarantee the cluster can never silently diverge from the repo.
verify.sh
# See what Argo thinks: is the app in sync with Git?
argocd app get web
# Watch a reconcile happen live after you merge a PR
argocd app sync web --watch
# Roll back to any previous Git state, this is your 'undo'
argocd app history web
argocd app rollback web <revision>
⚠️ Turn on selfHeal deliberately: selfHeal is wonderful until an incident, when someone urgently scales a deployment by hand and the controller helpfully reverts it 30 seconds later. The fix isn't to disable selfHeal; it's culture: in a GitOps world, the emergency fix is also a fast PR. Make merging trivial so nobody is tempted to fight the controller.
What GitOps actually buys you
- Audit for free. Every change to production is a git commit, author, timestamp, diff, review, all in your history. No separate change log to maintain.
- Rollback = git revert. Bad release? Revert the commit; the controller reconciles the cluster back. No special tooling, no remembering which pipeline run was good.
- Self-healing. Drift from manual edits or partial failures is detected and corrected automatically. The cluster trends toward the repo, always.
- Disaster recovery is reproducible. Lost the cluster? Point a fresh controller at the same repo and it rebuilds the declared state.
- Least-privilege CI. Your pipeline only needs to write to Git, not to the cluster, shrinking the blast radius of a compromised runner.
Common mistakes that cost hours
- Putting app code and config in the same repo. Mixing them means every code commit churns the deploy controller. Keep a separate config repo (or at least a separate path) as the source of truth.
- Committing plaintext secrets to Git. Git is now your source of truth, including your secrets if you're careless. Use Sealed Secrets, SOPS, or an external secrets operator. Never raw base64.
- Forgetting prune, then wondering why deleted manifests linger. Without prune, removing a file from Git leaves the resource orphaned in the cluster. Git stops being complete truth.
- Fighting selfHeal during incidents. Hand-editing a self-healing app starts a tug-of-war with the controller. Make emergency PRs fast instead.
- No environment promotion strategy. Pointing dev and prod at the same branch means every merge hits production. Use separate paths, overlays, or branches per environment.
- Treating the controller as fire-and-forget. GitOps controllers need monitoring too, a wedged ArgoCD silently stops reconciling, and drift creeps back in unnoticed.
Takeaways
GitOps in six lines
- Declare desired state in Git; a controller continuously reconciles the cluster to match.
- Pull beats push: cluster credentials stay in the cluster, and drift self-heals.
- Every production change is a reviewed, audited git commit, for free.
- Rollback is git revert; disaster recovery is point-a-controller-at-the-repo.
- prune + selfHeal make Git the complete, authoritative truth, use them on purpose.
- Keep config separate from app code, and never commit raw secrets.
Where to go next
GitOps sits on top of Kubernetes and Git workflows, strengthen both, then go deep on the controller:
- Kubernetes in Production: Beyond the Tutorial, the manifests your GitOps repo will hold.
- Git Workflows for Production Teams, because in GitOps, your branch strategy is your deploy strategy.
- Hands-on kubectl lab, get fluent with the cluster the controller manages.
- Hands-on Helm lab, package the manifests ArgoCD and Flux deploy.
Originally published on TheSimplifiedTech, where this guide is interactive, with in-browser terminal labs and diagrams. Learn cloud and DevOps by doing, no videos.
Top comments (0)