CoursesArgo CDGitOps & what Argo CD is

GitOps & what Argo CD is

Git as the source of truth.

Advanced12 min · lesson 1 of 12

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.

Pull does not mean safe
Moving the credentials inside the cluster removes one path, not every path. Argo CD deploys whatever the branch it watches says. If an attacker lands a commit in that branch, or swaps the container image a manifest points to, the change flows in on the next sync as if you had made it. GitOps shifts your defense onto the Git repo and the image registry. Protect those as hard as you would protect the cluster itself.

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.

terminal
kubectl get pods -n argocd
output
NAME READY STATUS RESTARTS AGE
argocd-application-controller-0 1/1 Running 0 5d
argocd-applicationset-controller-6b8d9f7c4-2xq9r 1/1 Running 0 5d
argocd-dex-server-7c9b6d5f4-h8vkm 1/1 Running 0 5d
argocd-notifications-controller-5d8c7b6f9-9wq2t 1/1 Running 0 5d
argocd-redis-6f7d8c9b5-tzn4c 1/1 Running 0 5d
argocd-repo-server-8c9d7b6f5-r6k7d 1/1 Running 0 5d
argocd-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.

guestbook-app.yaml
apiVersion: argoproj.io/v1alpha1
kind: Application
metadata:
name: guestbook
namespace: argocd
spec:
project: default
source:
repoURL: https://github.com/org/deploy-repo.git
targetRevision: main # branch, tag, or commit to track
path: guestbook # folder in the repo holding the manifests
destination:
server: https://kubernetes.default.svc
namespace: default
syncPolicy:
automated:
prune: true # delete resources removed from Git
selfHeal: 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.

The reconciliation loop
1Commit to Git
you push the desired state
2repo-server fetches
clones the repo, renders manifests
3controller compares
desired in Git vs live in cluster
4apply the difference
only what actually changed
5report status
Synced and Healthy, or not
6loop again
every ~3 min and on drift

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.

terminal
argocd app list
output
NAME CLUSTER NAMESPACE PROJECT STATUS HEALTH SYNCPOLICY CONDITIONS REPO PATH TARGET
argocd/guestbook https://kubernetes.default.svc default default Synced Healthy Auto-Prune <none> https://github.com/org/deploy-repo.git guestbook main
terminal
argocd app get guestbook
output
Name: argocd/guestbook
Project: default
Server: https://kubernetes.default.svc
Namespace: default
URL: https://argocd.example.com/applications/guestbook
Repo: https://github.com/org/deploy-repo.git
Target: main
Path: guestbook
SyncWindow: Sync Allowed
Sync Policy: Automated (Prune)
Sync Status: Synced to main (a1b2c3d)
Health Status: Healthy
GROUP KIND NAMESPACE NAME STATUS HEALTH HOOK MESSAGE
Service default guestbook-ui Synced Healthy service/guestbook-ui unchanged
apps 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.

terminal
kubectl scale deployment guestbook-ui --replicas=5 -n default
kubectl get deployment guestbook-ui -n default
output
deployment.apps/guestbook-ui scaled
NAME READY UP-TO-DATE AVAILABLE AGE
guestbook-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.

terminal
kubectl get deployment guestbook-ui -n default
output
NAME READY UP-TO-DATE AVAILABLE AGE
guestbook-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.

terminal
git revert --no-edit c3d4e5f
git push origin main
output
[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 threads
Compressing 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 0
To github.com:org/deploy-repo.git
c3d4e5f..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.

terminal
argocd app history guestbook
output
ID DATE REVISION
0 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.

Quick check
01A developer runs kubectl scale to bump a guestbook Deployment from 2 to 5 replicas on an app that Argo CD manages with automated sync, prune true, and selfHeal true. Git still says 2. What happens, and why?
Incorrect — Argo CD reconciles against live state continuously, not only on Git pushes. With selfHeal on it corrects out-of-band changes too.
Correct — selfHeal reverts live drift to the desired state in Git, which is why hand changes do not last and why they leave an audit trail.
Incorrect — Prune deletes resources that were removed from Git, not resources that merely drifted. The Deployment still exists in Git, so it is healed, not pruned.
Incorrect — Health measures whether the workload runs; sync measures whether it matches Git. Five replicas against a Git value of 2 is OutOfSync, so the app does not stay Synced.
02In the GitOps pull model this lesson describes, where do the cluster credentials live, compared with a traditional CI-push pipeline?
Incorrect — That is exactly the traditional push model GitOps moves away from, where internet-facing CI holds the keys.
Incorrect — In the pull model neither Git nor CI holds credentials that can apply to the cluster.
Correct — GitOps turns the arrow around, so an attacker who owns the CI runner has no cluster credentials to steal.
Incorrect — Deploys flow through Git and Argo CD, not through developer laptops holding cluster credentials.
03A commit bumped guestbook-ui to a broken image and it shipped. The app runs automated sync with selfHeal on. What is the correct way to roll back, according to this lesson?
Incorrect — With selfHeal on, Argo CD reverts your live change back to the broken image Git still names, so it would not stick.
Correct — Rollback follows the same path as any change: you revert the commit and let Argo CD pick it up on the next sync.
Incorrect — The lesson's rule is that you do not undo a bad deploy with a special button; the change must go through Git.
Incorrect — Deleting the Application removes management, not the bad image; it orphans or tears down the app rather than restoring the previous version.

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.

Related