Tools · Argo CD

Argo CD

Argo CD is the declarative GitOps continuous-delivery controller for Kubernetes: it runs inside your cluster, watches a Git repository for the manifests you say should be running, and never stops comparing that desired state against what is actually live — showing you the diff in a web UI and, if you let it, correcting reality automatically. It solves the platform problem of “nobody can tell me what is deployed where, or who changed it” by turning deployment from an action a human performs into a state a repository declares.

☺ Explain it like I’m 10

Imagine a huge LEGO city a hundred kids build together. Argo CD is the patient caretaker who keeps the instruction booklet on a shelf everyone can see. Every minute it walks the city, booklet in hand: “page 12 says a red tower — yep. Page 13 says a bridge — hmm, someone took the bridge apart.” It shows a big board with green ticks for what matches and orange marks for what doesn’t, and if you flip the “fix it yourself” switch, it quietly rebuilds the bridge. To change the city, you don’t move bricks — you edit the booklet.

🦫🤖Your hosts for this topic: Benny the Beaver & Recon the Robot — Benny wires up the repos, projects and app definitions, and Recon is the application-controller itself, looping forever to drag the cluster back onto the booklet.

What Argo CD is and the problem it solves

☺ Like you’re 10: It’s a robot living in your cluster whose only job is to make the cluster look exactly like a folder in Git.

Argo CD is a CNCF graduated project and the most widely deployed implementation of the GitOps pull model — a Kubernetes controller plus a UI, installed into a namespace (conventionally argocd). You give it a Git repository and a folder inside it; it renders whatever it finds there — plain YAML, a Kustomize overlay, or a Helm chart — into Kubernetes objects, compares them to the live objects, and reports a verdict.

The problem before Argo CD

Push-based delivery — a CI job holding a kubeconfig and running kubectl apply or helm upgrade — creates three durable headaches. CI needs long-lived cluster credentials, which makes the pipeline a juicy target. Nothing detects drift between merges. And there is no authoritative answer to “what is supposed to be running in prod?” — only a scroll through pipeline logs. Argo CD collapses all three: credentials stay inside the cluster, drift is detected continuously, and the repo is the answer.

The Application as the unit of delivery

Argo CD’s central idea is that a deployable thing is a custom resource called an Application. It is a three-part sentence: source (which repo, which path, which revision), destination (which cluster, which namespace), and project (which tenancy boundary it lives under). Everything else — automation, pruning, ordering, difference-ignoring — is policy hung off that sentence. Because the Application is itself a Kubernetes object, you can put Applications under Git too — the “app-of-apps” trick you meet below.

◆ Key idea

Argo CD keeps no external database. Its state lives in Kubernetes: Application, AppProject and ApplicationSet custom resources, plus Secrets holding repo and cluster credentials. Redis is a cache — delete the Redis pod and Argo CD is slow for a minute while it recomputes, but nothing is lost. That is why Argo CD itself is easy to make disposable.

What it is not

Argo CD is not a CI system: it does not build images or run tests — pair it with Tekton, Argo Workflows or a hosted CI. It is not a progressive-delivery engine either: canaries and blue/green belong to Argo Rollouts (or Flagger), which Argo CD happily syncs and reports health for. And it is not a secret manager — see Secrets Management and External Secrets Operator for the reference-in-Git, plaintext-never pattern.

Where it fits in a platform

☺ Like you’re 10: Argo CD sits between the “what we want” drawer and the real cluster, and it is the only thing allowed to open the cluster’s door.

In the layered platform model, Argo CD lives in the delivery plane: it consumes artefacts and config produced upstream and writes them into the Kubernetes substrate — deliberately as the only writer. Once a cluster is Argo-managed, a human kubectl apply is an exception that raises an alert, not a workflow.

Its neighbours

Upstream sits CI, which builds an image and commits a tag bump to the config repo. Beside Argo CD sit the renderers it invokes — Helm and Kustomize — and the policy engines that vet what it applies, Kyverno or OPA Gatekeeper, since admission control still runs on everything it sends. Downstream, Prometheus scrapes Argo CD’s metrics so you can alert on apps stuck OutOfSync or Degraded. And when Argo CD syncs Crossplane claims or Cluster API resources, the same loop provisions infrastructure and clusters, not just workloads.

Why platform teams reach for it

Two reasons dominate. The UI is a genuine developer-experience asset: teams see their own resource tree, diff and events without cluster credentials, killing a whole class of “can you check prod for me?” tickets. And ApplicationSet plus AppProject makes onboarding a team a directory creation rather than a ticket — the self-service moment.

CNPE domain relevance

Argo is named on the official CNPE tool list and lands squarely in GitOps & Continuous Delivery (25%) — one of the two joint-largest domains on the exam blueprint, alongside Platform APIs & Self-Service. It also brushes Platform Architecture & Infrastructure (it is the delivery control plane in nearly every reference design) and Security & Policy Enforcement (pull-based delivery, project-scoped RBAC, no cluster credentials in CI). Practise it against the GitOps practice tasks.

How it works — architecture and CRDs

☺ Like you’re 10: There are a few little helpers: one reads Git and turns it into instructions, one compares and fixes, one draws the website, and one remembers things quickly.

A standard install lays down a handful of workloads in the argocd namespace. Knowing which does what turns most Argo CD debugging into a two-minute job, because the logs you need are almost always in exactly one of them.

Git repo desired state repo-server renders Helm / Kustomize 🤖 application-controller diff · sync · self-heal Kubernetes API actual state Dex SSO / OIDC API / UI server 🦆 web UI + argocd CLI Redis cache only — not truth clone manifests apply watch live state The controller ships as a StatefulSet · everything else is a Deployment · state lives in CRs, not a database

The components

The application-controller is Recon: it watches every Application, asks the repo-server for rendered manifests, compares them against live objects, computes health, and performs syncs. In the standard install it runs as a StatefulSet — remember that, because scaling it means sharding across clusters, and because kubectl get deploy -n argocd appears to be missing the most important workload. The repo-server clones repos and renders them; Helm templating and Kustomize builds execute here, so “my chart won’t render” bugs live in its logs. The API/UI server (argocd-server) serves the web UI and the gRPC/REST API the CLI uses, and enforces RBAC. Redis caches manifests and resource trees. Dex is the optional identity broker bridging to your OIDC provider (drop it and point argocd-cm straight at an OIDC issuer if you already have one). Current versions of the standard install also lay down the applicationset-controller and the notifications-controller.

The custom resources it introduces

Argo CD adds three CRDs in the argoproj.io group. Application is one deployable unit. AppProject is a tenancy boundary: which repos an app may come from, which cluster/namespace destinations it may target, which cluster-scoped kinds it may create, and which roles can act on it. ApplicationSet is a templating factory that generates Applications. All three normally live in the argocd namespace, though Applications may live elsewhere when the project explicitly allows it.

Configuration lives in ConfigMaps, not a config file

Everything about the installation itself is declarative too, which is why an Argo CD install is trivially GitOps-managed by a second Argo CD (or by itself). Four objects in the argocd namespace carry it: argocd-cm is the main ConfigMap — resource health customisations, resource exclusions, OIDC settings; argocd-rbac-cm holds the RBAC policy as CSV lines plus a policy.default fallback (commonly role:readonly); argocd-cmd-params-cm supplies command-line parameters to the individual components; and the argocd-secret Secret holds the server signing key, the admin password hash and TLS material.

apiVersion: v1
kind: ConfigMap
metadata:
  name: argocd-rbac-cm
  namespace: argocd
  labels:
    app.kubernetes.io/name: argocd-rbac-cm
    app.kubernetes.io/part-of: argocd     # the label the standard install stamps on its own objects
data:
  policy.default: role:readonly           # everyone can look, nobody can act, by default
  policy.csv: |
    p, role:payments-deploy, applications, sync, payments/*, allow
    p, role:payments-deploy, applications, get,  payments/*, allow
    g, acme:payments-engineers, role:payments-deploy

The grammar is worth memorising: p lines are permissions — subject, resource, action, object, effect — and g lines bind an SSO group to a role. The object for an application is <project>/<application>, which is what makes AppProject the real tenancy hinge rather than a label.

Sync status vs health status — two different questions

Beginners conflate these constantly, and the exam likes the distinction. Sync status answers “does the cluster match Git?” Health status answers “is the thing actually working?” An app can be perfectly Synced and thoroughly Degraded — you faithfully deployed a broken image.

Status familyValueMeans
SyncSyncedLive state matches the rendered desired state at targetRevision.
OutOfSyncA difference exists — new commit not yet applied, or someone drifted the cluster.
UnknownArgo CD could not compare — usually a repo it can’t reach or manifests it can’t render.
HealthHealthyAll resources report ready per their health check (built-in for common kinds, Lua-scriptable for CRDs).
ProgressingStill rolling out; not yet a failure. Deployments sit here during an update.
DegradedA resource failed — crash-looping pods, failed Job, unavailable replicas.
SuspendedIntentionally paused, e.g. a suspended CronJob or a paused Rollout awaiting promotion.
MissingDeclared in Git but absent from the cluster.
UnknownHealth could not be assessed — no health check applies, or the comparison itself failed.
🦆 Dot’s-eye view

“When a deploy looks weird I open the Argo CD app tile and read the two badges. Green-and-green, it’s not the deploy. Synced but Degraded means my code is the problem — Argo shipped my bug faithfully. OutOfSync means my merge hasn’t landed yet, so I go bother the pipeline, not the SRE.”

The resources you will actually write

☺ Like you’re 10: Here are the actual bits of YAML you type — one for an app, one for ordering the steps, one for a team’s sandbox, one for stamping out many apps at once.

Four manifests carry ninety percent of real Argo CD work. Type them until they come out of your fingers — they are on Know Cold for a reason.

An Application with a real sync policy

Note carefully: prune and selfHeal both default to false. Adding automated: {} gives you auto-sync on new commits but neither deletion of removed resources nor drift correction — a very common exam trap and a very common production surprise.

apiVersion: argoproj.io/v1alpha1
kind: Application
metadata:
  name: checkout-prod
  namespace: argocd                       # Applications live in the Argo CD namespace
  finalizers:
    - resources-finalizer.argocd.argoproj.io   # cascade-delete live resources with the App
spec:
  project: payments                       # an AppProject, NOT a Kubernetes namespace
  source:
    repoURL: https://github.com/acme/platform-config.git
    targetRevision: main                  # branch, tag, or commit SHA — pin prod to a tag/SHA
    path: apps/checkout/overlays/prod     # a Kustomize overlay; repo-server renders it
  destination:
    server: https://kubernetes.default.svc  # in-cluster; or use `name:` for a registered cluster
    namespace: checkout
  syncPolicy:
    automated:
      prune: true        # DEFAULT false — delete live resources removed from Git
      selfHeal: true     # DEFAULT false — revert out-of-band drift back to Git
      allowEmpty: false  # default; refuse to apply a render that produced zero resources
    syncOptions:
      - CreateNamespace=true     # create spec.destination.namespace if absent
      - ServerSideApply=true     # SSA: better with large CRDs and shared field ownership
      - PruneLast=true           # delete removed resources only after everything else applied
    retry:
      limit: 5
      backoff: { duration: 5s, factor: 2, maxDuration: 3m }
  ignoreDifferences:
    - group: apps
      kind: Deployment
      jsonPointers:
        - /spec/replicas          # let an HPA own replica count without causing OutOfSync
ignoreDifferences is a scalpel, not a mute button

Every field you ignore is a field Git no longer governs. Ignoring /spec/replicas so an HPA can breathe is legitimate. Ignoring a whole spec because “it keeps going OutOfSync” hides a real conflict — usually a mutating webhook — and you rediscover it during an incident.

Ordering a sync: waves and hooks

A sync is not one big apply. Argo CD splits it into phases — PreSync, Sync, PostSync — and within a phase orders resources by sync wave, an integer annotation (negatives first, default 0). A wave must become healthy before the next starts. Hooks are ordinary resources — usually Jobs — annotated to run in a phase.

apiVersion: batch/v1
kind: Job
metadata:
  name: db-migrate
  annotations:
    argocd.argoproj.io/hook: PreSync                    # run BEFORE the main apply
    argocd.argoproj.io/hook-delete-policy: HookSucceeded # tidy up the Job when it passes
spec:
  template:
    spec:
      restartPolicy: Never
      containers:
        - name: migrate
          image: ghcr.io/acme/checkout-migrations:1.4.3
          command: ["/app/migrate", "up"]
---
apiVersion: v1
kind: Namespace
metadata:
  name: checkout
  annotations:
    argocd.argoproj.io/sync-wave: "-2"     # namespaces and CRDs go early
---
apiVersion: apps/v1
kind: Deployment
metadata:
  name: checkout
  annotations:
    argocd.argoproj.io/sync-wave: "1"      # after config, secrets and CRDs land
spec:
  replicas: 3
  selector:
    matchLabels:
      app.kubernetes.io/name: checkout     # must match the pod template labels exactly
  template:
    metadata:
      labels:
        app.kubernetes.io/name: checkout
    spec:
      containers:
        - name: checkout
          image: ghcr.io/acme/checkout:1.4.3
          ports:
            - containerPort: 8080

The hook annotation accepts PreSync, Sync, PostSync, SyncFail, PostDelete (run after the Application’s resources are deleted) and Skip (never apply this resource at all). Delete policies are HookSucceeded, HookFailed and BeforeHookCreation — the last being the default, which deletes the previous instance just before creating a new one so a re-run of a fixed-name Job does not collide. A PostSync smoke-test Job plus a SyncFail notifier Job is a tidy pattern worth stealing.

Two details that catch people out. A wave boundary is a health gate, not just an apply gate: Argo CD will not start wave n+1 until everything in wave n reports healthy, so a resource with no health check races ahead and one that never goes healthy stalls the sync until it times out. And waves are ordered within a phase — a PreSync hook always runs before every Sync-phase resource regardless of how negative that resource’s wave number is.

An AppProject for tenancy

Projects stop team A deploying into team B’s namespace, or creating cluster-scoped objects nobody approved. Treat them as the security boundary; see Governance & Compliance for the wider picture.

apiVersion: argoproj.io/v1alpha1
kind: AppProject
metadata:
  name: payments
  namespace: argocd
spec:
  description: Apps owned by the Payments team
  sourceRepos:
    - https://github.com/acme/platform-config.git   # only this repo may be a source
  destinations:
    - server: https://kubernetes.default.svc
      namespace: 'checkout*'                         # only namespaces matching this glob
  clusterResourceWhitelist: []                       # no cluster-scoped resources at all
  namespaceResourceBlacklist:
    - group: ''
      kind: ResourceQuota                            # tenants may not rewrite their own quota
  roles:
    - name: deployer
      policies:
        - p, proj:payments:deployer, applications, sync, payments/*, allow
      groups:
        - acme:payments-engineers                    # mapped from your SSO groups

ApplicationSet: one template, many apps

Hand-writing an Application per team per cluster stops scaling at about a dozen. Generators do the stamping: list (explicit items), git (one app per directory, or driven by JSON/YAML files in the repo), cluster (one per registered cluster), matrix (cross-product of two generators), merge (combine generators, later ones overriding parameters), SCM provider (one per repo in an org) and pull request (a preview environment per open PR). “Every app × every cluster” is the multi-cluster workhorse.

apiVersion: argoproj.io/v1alpha1
kind: ApplicationSet
metadata:
  name: addons-everywhere
  namespace: argocd
spec:
  goTemplate: true
  generators:
    - matrix:                                  # cross-product: each add-on × each cluster
        generators:
          - git:
              repoURL: https://github.com/acme/platform-config.git
              revision: main
              directories:
                - path: infrastructure/*       # one entry per add-on folder
          - clusters:
              selector:
                matchLabels: { env: prod }     # only clusters labelled env=prod
  template:
    metadata:
      name: '{{.path.basename}}-{{.name}}'     # e.g. cert-manager-eu-west-1
    spec:
      project: platform
      source:
        repoURL: https://github.com/acme/platform-config.git
        targetRevision: main
        path: '{{.path.path}}'
      destination:
        server: '{{.server}}'
        namespace: '{{.path.basename}}'
      syncPolicy:
        automated: { prune: true, selfHeal: true }
        syncOptions: [ CreateNamespace=true ]
◆ Key idea

The app-of-apps pattern is simpler than it sounds: write one Application whose path points at a folder full of other Application manifests. Argo CD syncs that folder, which creates the child Applications, which sync their own workloads. It bootstraps an entire cluster from a single kubectl apply. Reach for app-of-apps when the list is small and hand-curated, ApplicationSets when it should be generated.

Day-to-day commands

☺ Like you’re 10: A short list of things you type at the terminal to look at apps, push them, and undo them.

The argocd CLI talks to the API server, so log in first. Almost everything it does is also possible by editing the Application CR with kubectl — useful when the API server is what’s broken.

Install, log in, register

# Standard install (the manifest lives in the argo-cd GitHub repo)
kubectl create namespace argocd
kubectl apply -n argocd -f https://raw.githubusercontent.com/argoproj/argo-cd/stable/manifests/install.yaml
kubectl -n argocd rollout status statefulset/argocd-application-controller

# The bootstrap admin password is generated into a Secret
kubectl -n argocd get secret argocd-initial-admin-secret \
  -o jsonpath='{.data.password}' | base64 -d; echo

# Reach the API server without an Ingress, then log in
kubectl -n argocd port-forward svc/argocd-server 8080:443 &
argocd login localhost:8080 --username admin --insecure

# Register a private repo and an extra cluster
argocd repo add https://github.com/acme/platform-config.git --username git --password "$TOKEN"
argocd cluster add prod-eu-west-1        # uses the named kubeconfig context

Create, inspect, diff

argocd app create checkout-prod \
  --repo https://github.com/acme/platform-config.git \
  --path apps/checkout/overlays/prod \
  --revision main \
  --dest-server https://kubernetes.default.svc \
  --dest-namespace checkout \
  --project payments \
  --sync-policy automated --auto-prune --self-heal \
  --sync-option CreateNamespace=true

argocd app list                          # every app, with SYNC and HEALTH columns
argocd app get checkout-prod             # summary + per-resource tree
argocd app diff checkout-prod            # live vs desired; exits non-zero if it differs
argocd app manifests checkout-prod       # exactly what the repo-server rendered
argocd app logs checkout-prod --tail 50  # pod logs for the app's workloads

argocd app diff returning a non-zero exit code when differences exist makes it a natural drift check in a scheduled CI job or a pre-merge gate.

Sync, wait, and undo

argocd app sync checkout-prod                       # apply desired state now
argocd app sync checkout-prod --prune --dry-run     # preview what pruning would delete
argocd app sync checkout-prod --resource apps:Deployment:checkout   # one resource only
argocd app wait checkout-prod --health --timeout 300  # block until Healthy (great in pipelines)

argocd app set checkout-prod --revision v1.4.3      # repoint at a tag
argocd app history checkout-prod                    # numbered deployment history
argocd app set checkout-prod --sync-policy none     # rollback is refused while auto-sync is on
argocd app rollback checkout-prod 12                # redeploy history entry 12

# Refresh vs hard refresh: re-compare, or re-clone and re-render first
argocd app get checkout-prod --refresh
argocd app get checkout-prod --hard-refresh
rollback is a temporary lie

argocd app rollback redeploys a previous entry from the app’s deployment history — and Argo CD refuses to do it while automated sync is enabled, so you must turn automation off first. That is exactly the point: the moment you switch automation back on, Recon compares the app against Git again and cheerfully re-deploys the bad version. Rollback is incident response, not a fix. The real rollback is always a Git revert — see Release Engineering.

Gotchas and failure modes

☺ Like you’re 10: Here are the ways the tireless robot annoys people who haven’t met it before.

Self-heal fights the human

With selfHeal: true, a kubectl edit or kubectl scale on a managed resource is reverted within seconds. Engineers debugging under pressure find this maddening until someone explains it. The escape hatches, in order of preference: change Git; temporarily disable automation with argocd app set <app> --sync-policy none; or, for a planned experiment, add the resource to ignoreDifferences. Never disable self-heal cluster-wide to unblock one person — delivery triage has the decision tree.

Prune deletes things you meant to keep

Prune is symmetric with Git: remove the manifest, lose the resource. That is desirable — until someone moves a directory, renames a chart release, or an ApplicationSet generator briefly returns an empty list and a whole environment evaporates. Three guards: leave allowEmpty at its default of false and never flip it on to “fix” an empty render; annotate resources that must never be auto-deleted with argocd.argoproj.io/sync-options: Prune=false (PVCs holding data are the classic); and run --dry-run first on a busy cluster.

Immutable fields make an app OutOfSync forever

Some fields cannot be changed after creation: a Deployment’s spec.selector, a Job’s spec.template, a StatefulSet’s volumeClaimTemplates, a Service’s clusterIP. Change one in Git and every sync fails with a field is immutable error, leaving the app stuck OutOfSync while the error hides in the sync result. The fix is to replace rather than patch: argocd app sync <app> --replace, the Replace=true sync option, or the per-resource argocd.argoproj.io/sync-options: Replace=true annotation — all delete and recreate the object, so weigh the disruption. Better still, don’t mutate immutable fields.

The quieter operational traps

A few more, in the order you meet them. The application-controller is a StatefulSet, so kubectl get deploy -n argocd won’t show it and your “is Argo up?” check may lie. Big monorepos make the repo-server slow and memory-hungry — narrow the path, and add webhooks so Argo reacts to pushes instead of waiting for the default three-minute poll. Helm charts using lookup or random/time functions render differently every time, producing permanent meaningless drift. CRDs applied in the same wave as the custom resources that use them fail — put CRDs in an earlier wave. And an Application whose destination or repoURL the AppProject forbids fails with an error that reads like RBAC but is really a project problem. When stuck, open the troubleshooting playbook.

🦫 Benny’s workshop · 25 min

On a throwaway kind or minikube cluster: install Argo CD from the stable manifest and log in. Create an Application pointing at a public repo path with automated: {} only. Now kubectl scale the deployment by hand: Argo reports OutOfSync and does nothing — the default everyone forgets. Add selfHeal: true and scale again; reverted in seconds. Delete a manifest from your fork and confirm nothing is removed until you add prune: true. Finally add a PreSync hook Job that sleeps ten seconds and watch the phase ordering in the UI.

Alternatives and when to choose it

☺ Like you’re 10: Other robots can do this job too — here’s how to pick one.

The honest answer is that Flux and Argo CD are both excellent and both CNCF-graduated; the choice is about shape and team, not capability.

The comparison that decides it

OptionModelBest whenCosts you
Argo CDApplication-centric pull; one integrated product with a rich UIMany app teams need visibility without cluster access; you want sync waves, hooks and a diff view out of the boxHeavier install; an extra RBAC/SSO surface; the UI can become a shadow control plane if people click Sync instead of merging
FluxComposable toolkit of small controllers, API-firstBuilding bespoke platform primitives; you want built-in image automation and SOPS decryption, and only Kubernetes RBACNo official UI, so developer visibility must be built or bought
CI push (kubectl apply from a pipeline)Push, at merge time onlyOne small cluster, one small team, nothing more neededCluster credentials in CI; no drift detection — fails OpenGitOps principles 3 and 4
Helm/Kustomize aloneRenderers, not reconcilersAlways — they render for Argo CD or FluxAlone they answer “what do these manifests look like?”, never “is the cluster still correct?”
Argo Rollouts / FlaggerProgressive delivery controllersAlongside Argo CD, for canary or blue/green with automated analysisNot a substitute — they change how a workload updates, not where desired state comes from

A practical rule

Choose Argo CD when the platform’s customers are many application teams who benefit from seeing their own deployments; choose Flux when the platform team is composing its own APIs and values controllers over dashboards. Plenty of organisations run both — Flux for infrastructure add-ons, Argo CD for tenant workloads. See The Tool Landscape for how this sits among the other named projects, and CI/CD & Progressive Delivery for the pipeline half of the story.

🎬 At the Platform Guild
🦊

Foxy: I turned on auto-sync last week and it still didn’t undo my hand-edit. Broken?

🦫

Benny: Working exactly as documented. automated on its own only syncs new commits. prune and selfHeal are both false by default — you have to ask for them.

🤖

Recon: BEEP. I saw the drift. I reported OutOfSync. Nobody gave me permission to act. I am a very well-behaved robot.

👺

Gizmo: Just click Sync in the UI whenever prod looks wrong. Way faster than a pull request. 🤑

🐢

Timmy: And now the UI is the source of truth and Git is fiction. Clicking Sync is fine to accelerate what Git already says — never to deploy something Git doesn’t.

🦆

Dot: Honestly the resource tree is my favourite part. I can see my own pods going Progressing → Healthy without begging anyone for a kubeconfig.

Exam relevance and going further

☺ Like you’re 10: On exam day you can’t open Argo’s own website — so the YAML has to already be in your head.

“Argo” is on the official CNPE tool list and Argo CD is the most likely way a GitOps task is presented. Expect to install or inspect it, create or repair an Application, turn on the right sync policy, diagnose an OutOfSync or Degraded app, and explain the pull model.

The documentation allowlist — read this twice

⚠ Argo CD’s own docs are not available during the exam

During the CNPE the only documentation you may open is kubernetes.io/docs, kubernetes.io/blog, task-specific documentation explicitly linked in the exam’s Quick Reference box, and local man pages / /usr/share docs on the exam machine. argo-cd.readthedocs.io is not on that list. Unless a task’s Quick Reference hands you an Argo CD link, write the Application manifest from memory. Drill it from Know Cold — that page exists precisely for the manifests you cannot look up.

⚖ CNPA vs CNPE — That allowlist is a CNPE-specific mechanic; CNPA has no allowlist at all, because CNPA is fully closed-book — zero external resources, zero lookups of any kind, on anything. That’s stricter than CNPE, not looser. Even so, the Argo CD concepts on this page — the pull model, the sync/health distinction, ApplicationSet generators — are worth knowing cold, since this concept-level knowledge still matters for CNPA’s closed-book recall.

What to be able to do without notes

Write a valid Application from a blank file: apiVersion: argoproj.io/v1alpha1, kind: Application, the argocd namespace, the source/destination/project triple, prune and selfHeal (both default false), and CreateNamespace=true. Explain sync status versus health status and name the values of each. Name the components and say which renders Helm charts. Name four ApplicationSet generators. Say what an AppProject restricts. And know the CLI spine: argocd login, app create, get, diff, sync, set, wait, history, rollback — all in the command reference.

Official resources for after the exam

Outside the exam, the canonical sources are argo-cd.readthedocs.io (the Declarative Setup, Sync Options and ApplicationSet pages repay careful reading), the project site at argoproj.github.io/cd, the source at github.com/argoproj/argo-cd, and cncf.io/projects/argo. Pair this page with GitOps Workflows for the principles, Flux for the comparison, and the glossary when a term stops making sense.

🐢 Timmy’s checkpoint

1. Which Argo CD component renders a Helm chart into manifests, and which one applies them? 2. You set syncPolicy.automated: {} and nothing reverts a manual kubectl scale. Why, and what do you add? 3. An app shows Synced and Degraded — whose problem is it? 4. Your sync keeps failing with “field is immutable” on a Deployment. Name one fix and its cost. 5. Which ApplicationSet generator deploys every add-on folder to every production cluster? 6. During the exam, where can you look up the Application schema?

Check your answers
  1. The repo-server clones and renders (Helm, Kustomize, plain YAML); the application-controller diffs the result against live state and applies it. The application-controller runs as a StatefulSet.
  2. prune and selfHeal both default to false. Bare automated only syncs new commits; add selfHeal: true to revert drift (and prune: true for deletions).
  3. Yours, not the platform’s. Synced means Argo CD faithfully deployed what Git asked for; Degraded means the workload itself is unhealthy — a bad image, a crash loop, a failing probe.
  4. Replace instead of patch: argocd app sync <app> --replace, the Replace=true sync option, or the per-resource argocd.argoproj.io/sync-options: Replace=true annotation. The cost is that the object is deleted and recreated, which is disruptive — so prefer not to change immutable fields at all.
  5. A matrix generator combining a git directory generator (one entry per add-on folder) with a cluster generator selecting production clusters.
  6. You can’t — Argo CD’s documentation is not on the exam allowlist (kubernetes.io/docs, kubernetes.io/blog, task-specific Quick Reference links, and local man//usr/share docs only). Write it from memory; drill it on Know Cold.