GitOps & what Argo CD is
Git as the source of truth.
Set a thermostat to 21 degrees and walk away. It does not care who opened a window or lit the oven. It keeps nudging the heating on and off until the room matches the number you set. Argo CD works like that thermostat. The number you set is the desired state of your cluster, and the place you write it down is Git.
A few terms first, in plain words. Kubernetes (often shortened to K8s) is the system that runs your containers across a fleet of machines, deciding what runs where and restarting anything that dies. Git is the version-control system that records every change to a set of text files, so you can see who changed what and when, and roll any of it back. The CD in Argo CD stands for continuous delivery, which means getting new versions of software out to where they run, without a human doing it by hand. Combine those ideas, Git plus automated delivery, and you have the makings of GitOps.
Git as the source of truth
GitOps is a way of running infrastructure where one Git repository holds the complete description of what should be running, and software inside the cluster continuously works to make reality match that description. You do not open a shell and change production by hand. You change files in Git, a controller notices, and it reconciles the cluster back to what the files say. The description is declarative: instead of a script of steps to run, you store the end state you want as Kubernetes manifests (text files, written in a format called YAML, that describe objects like Deployments and Services).
The Git repo is the master recipe book in a busy kitchen. Every cook follows it. If the book says the soup takes two onions and someone at the stove throws in five, the pot is now out of step with the book. In GitOps the book always wins. A controller walks over, notices the mismatch, and quietly fixes the pot back to two. Nobody has to remember what the soup was supposed to be, because it is written down, dated, and signed.
Push versus pull, and who holds the keys
This is where GitOps changes your security posture. It does not add a new security feature. It moves a set of keys. In a traditional delivery pipeline the CI system (continuous integration, the automation that builds and tests your code, such as GitHub Actions, GitLab CI, or Jenkins) holds credentials for the cluster and pushes changes in. It uses kubectl (the Kubernetes command-line tool) to apply changes from the outside. Those cluster credentials then live in a system that faces the internet, runs code from every pull request, and pulls in hundreds of third-party build steps. It is a busy front door with a lot of keys hanging on a hook beside it.
GitOps turns the arrow around. The cluster credentials live inside the cluster, held by Argo CD, which reaches out and pulls from Git. CI never touches the cluster. Its work ends when it pushes a commit or publishes a container image. An attacker who fully owns your CI runner can poison a build, but the runner has no cluster credentials to steal, so it cannot apply to production directly. You have taken the keys off the front-door hook and locked them in a room only the thermostat can enter.
What Argo CD actually is
Argo CD is a small set of components rather than a single program, and they run inside the cluster, usually in a namespace called argocd (a namespace being a way to group and wall off a set of resources within a cluster). The application-controller is the reconciler, the thermostat itself: it compares desired state against live state and applies the difference. The repo-server clones your Git repositories and renders the final manifests. The server component serves the API (application programming interface, the machine-to-machine entry point), the web UI (user interface), and the CLI (command-line interface). Redis is an in-memory cache, and dex is an optional piece for single sign-on. You can see them all running.
kubectl get pods -n argocd
NAME READY STATUS RESTARTS AGEargocd-application-controller-0 1/1 Running 0 5dargocd-applicationset-controller-6b8d9f7c4-2xq9r 1/1 Running 0 5dargocd-dex-server-7c9b6d5f4-h8vkm 1/1 Running 0 5dargocd-notifications-controller-5d8c7b6f9-9wq2t 1/1 Running 0 5dargocd-redis-6f7d8c9b5-tzn4c 1/1 Running 0 5dargocd-repo-server-8c9d7b6f5-r6k7d 1/1 Running 0 5dargocd-server-7d8c9b6f5-mn8xp 1/1 Running 0 5d
You tell Argo CD what to manage with an object called an Application. The Application is itself a Kubernetes resource, a custom resource (a new type that Kubernetes did not ship with, added by Argo CD when you install it). One Application points at a repository, a path inside it, a branch or tag to track, and where in the cluster to deploy. Here is a minimal one for a demo app called guestbook.
apiVersion: argoproj.io/v1alpha1kind: Applicationmetadata:name: guestbooknamespace: argocdspec:project: defaultsource:repoURL: https://github.com/org/deploy-repo.gittargetRevision: main # branch, tag, or commit to trackpath: guestbook # folder in the repo holding the manifestsdestination:server: https://kubernetes.default.svcnamespace: defaultsyncPolicy:automated:prune: true # delete resources removed from GitselfHeal: true # revert changes made outside Git
Read the two flags under automated closely, because they decide how strict the thermostat is. With prune set to true, deleting a manifest from Git deletes the live resource on the next sync. With selfHeal set to true, any change made to the cluster by hand gets reverted back to what Git says. Turn both on and Git is the only authority that sticks. Leave them off and Argo CD only tells you about drift (the live cluster no longer matching Git) and waits for you to press sync.
Seeing sync and health
Argo CD reports two separate signals, and it helps to keep them apart. Sync status answers one thing: does the live cluster match Git. Health status answers another: is the workload actually working. Back to the thermostat: sync is whether the room matches the setting you dialed in, health is whether the furnace is running at all. They move independently. An app can be Synced but Unhealthy when Git is correct yet the pod (the smallest thing Kubernetes runs, one or more containers together) keeps crashing on startup. It can be OutOfSync but Healthy when someone changed the live cluster, the app still serves traffic, but it no longer matches the book. Start with a list of everything Argo CD manages.
argocd app list
NAME CLUSTER NAMESPACE PROJECT STATUS HEALTH SYNCPOLICY CONDITIONS REPO PATH TARGETargocd/guestbook https://kubernetes.default.svc default default Synced Healthy Auto-Prune <none> https://github.com/org/deploy-repo.git guestbook main
argocd app get guestbook
Name: argocd/guestbookProject: defaultServer: https://kubernetes.default.svcNamespace: defaultURL: https://argocd.example.com/applications/guestbookRepo: https://github.com/org/deploy-repo.gitTarget: mainPath: guestbookSyncWindow: Sync AllowedSync Policy: Automated (Prune)Sync Status: Synced to main (a1b2c3d)Health Status: HealthyGROUP KIND NAMESPACE NAME STATUS HEALTH HOOK MESSAGEService default guestbook-ui Synced Healthy service/guestbook-ui unchangedapps Deployment default guestbook-ui Synced Healthy deployment.apps/guestbook-ui unchanged
Drift, self-heal, and rollback
Time to make Argo CD earn its keep. Git says this Deployment runs two replicas (two identical copies of the pod behind one Deployment). Change the live cluster by hand, the way a tired engineer might during an incident, and watch what happens with selfHeal turned on.
kubectl scale deployment guestbook-ui --replicas=5 -n defaultkubectl get deployment guestbook-ui -n default
deployment.apps/guestbook-ui scaledNAME READY UP-TO-DATE AVAILABLE AGEguestbook-ui 5/5 5 5 5d
Five replicas, for a few seconds. The application-controller notices the live cluster no longer matches Git, marks the app OutOfSync, and because selfHeal is on, scales it straight back to the two that Git asked for. You did not run anything. The thermostat did its job. Wait a moment and check again.
kubectl get deployment guestbook-ui -n default
NAME READY UP-TO-DATE AVAILABLE AGEguestbook-ui 2/2 2 2 5d
That behavior is a gift to a defender. An attacker who changes a live resource, say adding a sidecar container (an extra container slipped in alongside the real one) that phones home, sees their change reverted within seconds and leaves a trail: the app flipped OutOfSync and the controller logged the correction. To make a change that lasts, you have to go through Git, which means it is reviewed, dated, and attributed. Rollback follows the same rule. You do not undo a bad deploy with a special button, you revert the commit.
git revert --no-edit c3d4e5fgit push origin main
[main d4e5f6a] Revert "bump guestbook-ui image to v2.1.0"1 file changed, 1 insertion(+), 1 deletion(-)Enumerating objects: 7, done.Counting objects: 100% (7/7), done.Delta compression using up to 8 threadsCompressing objects: 100% (3/3), done.Writing objects: 100% (4/4), 402 bytes | 402.00 KiB/s, done.Total 4 (delta 2), reused 0 (delta 0), pack-reused 0To github.com:org/deploy-repo.gitc3d4e5f..d4e5f6a main -> main
On the next sync Argo CD picks up the new commit and rolls the app back to the previous image, the same path any change takes. The deploy history records the revert as a normal entry, so the audit trail stays honest about what ran and when.
argocd app history guestbook
ID DATE REVISION0 2026-07-18 09:14:02 +0000 UTC main (a1b2c3d)1 2026-07-19 11:22:41 +0000 UTC main (b2c3d4e)2 2026-07-20 08:05:17 +0000 UTC main (c3d4e5f)3 2026-07-20 08:42:55 +0000 UTC main (d4e5f6a)
Why Argo CD is a high-value target
Everything that makes Argo CD pleasant to operate also makes it dangerous to lose. To reconcile clusters it holds credentials for every cluster it manages, and it applies whatever the repositories it trusts tell it to apply. A compromise of Argo CD, or of a repo it watches, or of the Git server hosting that repo, is a compromise of those clusters. The self-heal that reverts an attacker's live changes will also faithfully re-apply an attacker's malicious commit, and keep re-applying it even after you delete the resource by hand, because to the controller you are the one causing drift. Once the rule is change Git and the cluster follows, Git and Argo CD together are your deployment authority.
So treat both as production infrastructure from the first install. Require reviews and, where you can, signed commits on the branches Argo CD tracks, so a single stolen developer token cannot ship code alone. Lock down who can create or edit Application objects, since an Application is a live instruction to deploy from a repo of the author's choosing. Scope what each Argo CD project is allowed to touch, which clusters, namespaces, and resource kinds, so one bad Application cannot reach everything. And when you need to know what actually happened, you have two clean records to read: git log for who authored a change, and argocd app history for when it went live.
Try this
Run kubectl get pods -n argocd on a scratch host or disposable cluster and read the output against what this lesson described. Then change one input so it fails, and re-run: the error you get is the one you will meet in production.
Takeaway
The trap worth remembering here: pull does not mean safe. Check that on your own systems before you need to, because it is cheaper to find on a quiet afternoon than during an incident.