2. The architecture
Argo CD · 2. The architectureArgo CD is a handful of stateless processes plus a cache. There is no database, and that is a design decision worth understanding early, because it explains almost everything about how it scales and how it fails.
The whole thing on one page
Section titled “The whole thing on one page” ┌──────────────────────────────────────────────────────────┐ │ ARGO CD (a namespace in a Kubernetes cluster) │ │ │ │ ┌────────────────┐ ┌──────────────────────────┐ │ │ │ API SERVER │◄───────►│ REDIS │ │ │ │ UI, CLI, gRPC │ │ cache ONLY — no truth │ │ │ │ auth, RBAC │ │ rendered manifests, │ │ │ └───────┬────────┘ │ live resource tree │ │ │ │ └──────────▲───────────────┘ │ │ │ │ │ │ ┌───────▼────────────────────┐ │ │ │ │ APPLICATION CONTROLLER │────────┘ │ │ │ the reconciliation loop: │ │ │ │ diff, sync, health │ │ │ └───────┬──────────────┬─────┘ │ │ │ │ │ │ ┌───────▼────────┐ │ ┌────────────────────────┐│ │ │ REPO SERVER │ │ │ DEX (optional) ││ │ │ git clone, │ │ │ SSO / OIDC federation ││ │ │ helm template,│ │ └────────────────────────┘│ │ │ kustomize build│ │ │ │ └───────┬────────┘ │ │ └──────────┼──────────────┼────────────────────────────────┘ │ │ ▼ ▼ ┌──────────┐ ┌──────────────────────────┐ │ GIT │ │ TARGET CLUSTER(S) │ │ (+Helm │ │ (often the same cluster)│ │ repos) │ └──────────────────────────┘ └──────────┘Five processes. Learn what each does and every troubleshooting session gets shorter, because the first question is always which one is unhappy.
The application controller
Section titled “The application controller”This is Argo CD. Everything else is support.
It runs the reconciliation loop for every Application:
forever, for each Application: 1. ask the repo server for the DESIRED manifests 2. read the LIVE resources from the target cluster 3. compute the DIFF 4. set sync status: Synced / OutOfSync 5. assess HEALTH of every resource 6. if auto-sync is on and there is a diff: APPLYTwo things it maintains that matter operationally.
A watch on every managed cluster. Not a poll — the controller holds informers on the resource kinds it manages, so it learns about live changes immediately. This is the Kubernetes watch mechanism doing the work, and it is why drift is usually detected in seconds rather than at the next refresh.
An in-memory resource tree per cluster. Every managed resource, its owner references, and its children. This is how the UI can show you that an Application owns a Deployment which owns a ReplicaSet which owns three Pods. It is also the single largest consumer of memory in Argo CD, and chapter 13 is mostly about it.
The controller is the bottleneck and the thing you scale. It is the only component that is not trivially horizontally scalable, because each cluster is owned by exactly one controller shard. Chapter 13 covers sharding.
The repo server
Section titled “The repo server”Its job is narrow and it is where most of the CPU goes: turn a git reference into a set of Kubernetes manifests.
input: repo URL, revision, path, and how to render output: a list of rendered Kubernetes objectsIt clones the repository, checks out the revision, works out what kind of source it is, and runs the right tool:
| Detected | Command run |
|---|---|
Chart.yaml |
helm template |
kustomization.yaml |
kustomize build |
.jsonnet |
jsonnet |
plain .yaml |
read them as-is |
a plugin’s discover matches |
your config management plugin |
Three properties worth knowing:
It is stateless and horizontally scalable. Add replicas freely; this is the easy scaling knob.
It caches aggressively in Redis, keyed by repo, revision and rendering parameters. A rendered manifest for a given commit is computed once and reused by every Application that needs it. This is why the first sync after a commit is slow and subsequent ones are instant.
It executes code from your repository. helm template runs Helm’s templating; a config management plugin runs an arbitrary binary. The repo server is therefore a genuine security boundary, and chapter 13 explains why it runs with a restricted profile and why untrusted repositories are a real risk.
The API server
Section titled “The API server”Confusingly named — it is not the Kubernetes API server. It serves:
- the web UI
- the gRPC/REST API that
argocdCLI talks to - authentication (local accounts, SSO via OIDC, or Dex)
- RBAC — who may see, sync, or delete which Applications
- webhook endpoints, so a git push can trigger an immediate refresh
It is stateless and horizontally scalable. It is also not on the critical path for reconciliation — the application controller keeps syncing perfectly well with the API server down. You lose the UI and the CLI, not the deployments. That distinction is worth knowing at 3 a.m.
The most misunderstood component. Two facts, in order of importance:
Redis holds no source of truth. It is a cache: rendered manifests, resource trees, revision metadata. Flush it entirely and Argo CD rebuilds everything from git and from the clusters.
Argo CD is nonetheless unusable without it. The controller and API server treat cache misses as expensive rather than as errors, so a Redis outage does not corrupt anything — it makes everything slow enough to look broken, with UI timeouts and reconciliation falling badly behind.
So: do not persist Redis, do not back it up, and do not ignore it either. The default single-replica deployment is a genuine single point of degradation, and the HA install ships Redis with sentinel for this reason. If you have read the Redis guide, the failover semantics there apply directly here — and losing a few cached manifests is exactly the kind of data loss that does not matter.
An optional identity broker for SSO with providers that Argo CD’s built-in OIDC support cannot handle directly — SAML, LDAP, GitHub organisations, and similar. If your provider speaks plain OIDC, configure it directly and do not run Dex at all.
Where the state actually lives
Section titled “Where the state actually lives”There is no Argo CD database. Everything durable is a Kubernetes object in Argo CD’s namespace:
| What | Stored as |
|---|---|
| Applications | Application CRs |
| Projects | AppProject CRs |
| Repository credentials | Secrets labelled argocd.argoproj.io/secret-type: repository |
| Cluster credentials | Secrets labelled ...secret-type: cluster |
| Configuration | ConfigMaps argocd-cm, argocd-rbac-cm, argocd-cmd-params-cm |
| Local users, tokens | Secret argocd-secret |
This has a consequence people find surprising and then come to appreciate: Argo CD’s own configuration is Kubernetes resources, so Argo CD can manage itself. An Application pointing at the repository that contains Argo CD’s own manifests is a normal and recommended setup. Chapter 11 covers the bootstrap ordering that makes it work, and chapter 14 covers the one way it goes badly wrong.
It also means backing up Argo CD is backing up those objects — or, better, keeping them in git in the first place, at which point there is nothing to back up. Which is the point.
The path of a change
Section titled “The path of a change”End to end, the way it actually happens:
1. a commit lands in the manifest repository
2. either: a webhook hits the API server → immediate refresh or the controller's polling interval fires (default: every 3 minutes)
3. the controller asks the repo server for manifests at the new revision
4. the repo server clones/fetches, renders, caches the result in Redis, returns it
5. the controller compares desired against live → OutOfSync
6. if auto-sync: apply the changed resources, respecting sync waves and hooks
7. watch the resources become healthy → Synced + HealthyStep 2 is the one worth configuring. The three-minute default poll is the single most common reason people say “Argo CD is slow.” It is not slow; it has not looked yet. Configure a webhook and step 2 becomes instantaneous. Chapter 13 covers this and the reasons the poll interval exists at all.
Chapter 5 traces every one of these steps in detail.
HA versus non-HA
Section titled “HA versus non-HA”The install manifests come in two shapes and the difference is real:
| Default | HA | |
|---|---|---|
| Application controller | 1 replica | 1 replica per shard |
| Repo server | 1 | 2+ |
| API server | 1 | 2+ |
| Redis | 1 pod, no failover | 3 + sentinel |
| Suitable for | trying it out, small teams | anything you depend on |
Use the HA manifests for anything that matters. The default install is genuinely a demo configuration, and the component whose failure hurts most is the one it makes least redundant: Redis.
Note that even in HA, the application controller is one replica per shard, not several per shard. It is not active-active. Each shard’s controller is the single owner of its clusters, and it uses a Kubernetes lease for leader election if you run more. Chapter 13 explains why the design is this way and what it means for scaling.
What to take from this chapter
Section titled “What to take from this chapter”- Five components: application controller (the reconciliation loop), repo server (git → manifests), API server (UI, CLI, auth, RBAC, webhooks), Redis (cache), Dex (optional SSO).
- The application controller is Argo CD. It holds watches on every managed cluster and an in-memory resource tree, which is the main memory consumer.
- The repo server is where the CPU goes — it runs
helm templateandkustomize build, is stateless and easy to scale, caches in Redis, and executes code from your repositories. - The API server is not on the reconciliation critical path. With it down you lose the UI and CLI, not deployments.
- Redis holds no truth but Argo CD is unusable without it. Do not persist it; do run it HA.
- There is no database. All durable state is Kubernetes objects — Applications, AppProjects, labelled Secrets, ConfigMaps — which is why Argo CD can manage itself.
- A change flows: commit → webhook or 3-minute poll → repo server renders → controller diffs → apply → health. The 3-minute poll is why people think Argo CD is slow; use webhooks.
- Use the HA manifests for anything real. The controller is one replica per shard by design, not active-active.
Next: the Application resource itself — every field that matters, and the ones that quietly change behaviour.