The problem
You now have overlays in git (26.2), and you applied them by hand with kubectl apply -k. GitOps (26.1) says a program in the cluster should do that for you, and keep doing it. Argo CD is that program. This lesson is how it is built, the one object you give it (the Application), how to read what it tells you, and the argocd command.
What you need to know already: 26.1 (GitOps, pull, reconcile, drift), 26.2 (kustomize overlays), 15.9 (the reconciliation loop: a controller compares desired and actual state and fixes the difference), 15.16 (Deployments and ReplicaSets), 15.26 (namespaces, labels and annotations).
What Argo CD is
Argo CD is a set of programs you install into the cluster, in their own namespace argocd. Together they are a controller (15.9): a loop that reads the desired state - here from git, not from the Kubernetes API - and makes the cluster match.
The pieces
You do not have to operate these today, but their names show up in pod lists and error messages:
argocd-server the API, the web UI, and what the argocd CLI talks to
(logins, permissions, single sign-on)
argocd-repo-server clones git and RENDERS the manifests: runs helm template,
kustomize build, or reads plain YAML. Keeps no state.
argocd-application-controller the reconciler: compares the rendered desired state with
the live cluster, works out sync + health, runs syncs
argocd-redis a cache (redis = a small in-memory database)
argocd-dex-server single sign-on: lets people log in with Entra ID (22.8),
GitHub or a company directory
argocd-applicationset-controller creates Applications from a template (26.16)
Render means turn a chart or an overlay into the plain Kubernetes YAML it stands for - what helm template (25.19) or kubectl kustomize (26.2) print.
Everything about Argo CD is itself configured with Kubernetes objects in the argocd namespace: its own object kinds Application and AppProject (added to the API as CRDs, custom resource definitions - new kinds of object a program adds to Kubernetes), the argocd-cm and argocd-rbac-cm ConfigMaps (settings and who may do what), and Secrets holding repo passwords and cluster credentials. So Argo CD's own setup can live in git too.
The Application
An Application is one entry in Argo CD's list of things to deploy: "take this path of this repo, and keep it applied to this namespace of this cluster".
apiVersion: argoproj.io/v1alpha1
kind: Application
metadata:
name: payments-dev
namespace: argocd # Applications live in argocd's namespace
spec:
project: default # an AppProject: which repos/clusters/namespaces are allowed (26.16)
source: # WHERE the desired state is
repoURL: https://git.lab/pay/payments-gitops.git
targetRevision: HEAD # which version: a branch, a tag, or a commit (HEAD = latest on the default branch)
path: overlays/dev # kustomization.yaml -> kustomize; Chart.yaml -> helm; else plain YAML
# helm: { valueFiles: [values-dev.yaml], parameters: [...] } for charts
destination: # WHERE to apply it
server: https://kubernetes.default.svc # the cluster Argo CD itself runs in
namespace: pay-dev # namespace for objects that do not name one
syncPolicy: # leave it out = you sync by hand
automated: { prune: true, selfHeal: true } # 26.9
syncOptions: [CreateNamespace=true] # create pay-dev if missing
A sync is Argo CD applying what git renders to the cluster - its kubectl apply. With no syncPolicy you trigger each sync yourself.
Two statuses, always read both
Sync status answers "does the cluster match git?":
Synced every object Argo CD tracks matches what git renders
OutOfSync something differs, is missing, or exists in the cluster but not in git
Unknown Argo CD could not render or compare (bad YAML, repo unreachable)
Health status answers "does what is running work?":
Healthy Deployments rolled out, pods ready, Services with endpoints...
Progressing a rollout in progress (or stuck, until progressDeadlineSeconds runs out)
Degraded failed: a Deployment past its deadline, a crash loop, a failed Job
Missing the object is in git but not in the cluster
Suspended paused (a suspended CronJob, a paused rollout)
They are independent, and the pair tells you where to look:
- Synced + Degraded: git was applied correctly, and the version in git is broken. Fix it in git (roll forward or revert).
- OutOfSync + Healthy: the cluster works but differs from git - drift, or a commit not synced yet.
- Synced + Progressing for a long time: the rollout is stuck (readiness probe, image pull, quota - 18.28).
When does Argo CD notice a commit?
Two ways:
- Polling: the controller re-reads git and re-renders every app every 3 minutes (the
timeout.reconciliationsetting, 180s by default). - Webhook: git calls Argo CD's API the moment you push (a webhook is an HTTP call one system makes to another when something happens). Then it is immediate.
"I pushed and nothing happened" is usually the 3 minutes. argocd app get APP --refresh makes it re-read git now; --hard-refresh also throws away its cache of rendered manifests. git.lab in this lab sends webhooks, so a push is seen within seconds.
Separately, the controller watches the cluster itself, so a change made with kubectl shows up as OutOfSync within seconds.
How it knows what it owns
When Argo CD applies an object it adds an annotation (15.26: a key-value note on an object) with the app's name:
argocd.argoproj.io/tracking-id: payments-dev:apps/Deployment:pay-dev/payments-api
(Older versions used the app.kubernetes.io/instance label instead, which clashed with Helm charts that set the same label.) An object with this annotation that is no longer in git is an orphan - left behind, a candidate for pruning (26.9). An object without it is not Argo CD's business.
The CLI
argocd is the command-line client. The shape is argocd app VERB APPNAME:
argocd admin initial-password -n argocd # print the first admin password (kept in a Secret)
argocd login argocd.lab --username admin # open a session with the server; asks for the password
argocd login --core # no server: talk to the Kubernetes API directly
argocd app list # every Application, one line each
argocd app get payments-dev [--refresh] # summary, statuses, every resource with its status
argocd app diff payments-dev # git vs live, as a diff (exit code 1 = there are differences)
argocd app create NAME --repo URL --path DIR --dest-server URL --dest-namespace NS
# register an Application (manual sync unless --sync-policy)
argocd app sync payments-dev [--prune] # apply now; prints each resource's result
argocd app wait payments-dev --health --timeout 180 # block until Healthy (or give up after 180 s)
argocd app history payments-dev # past syncs, each with an ID and the git commit
argocd app rollback payments-dev ID # apply an older ID's content again (manual sync only)
argocd app set payments-dev --sync-policy automated --self-heal --auto-prune
# change settings: here, turn on auto-sync (26.9)
argocd app rollback is refused while auto-sync is on (rollback cannot be initiated when auto-sync is enabled): the controller would immediately sync back to whatever git says. In GitOps the real rollback is git revert (25.2).
What you can now do
- Describe what Argo CD's parts do and write an Application for a path in a repo.
- Read sync status and health status together and say where the problem is.
- Use
argocd app get / diff / sync / historyto inspect and deploy an app.