29.2 ArgoCD Architecture: Application Controller, Repo Server, API Server
Right, let’s pull back the curtain. You’re about to deploy your entire application infrastructure by pushing a YAML file to a Git repo. It feels like magic, but magic you don’t understand is just a fancy way to get yourself into a spectacular mess. So let’s break down the three wizards behind the curtain: the Application Controller, the Repo Server, and the API Server. Knowing how they bicker and work together is the difference between “it just works” and “why is everything on fire?”
At its heart, ArgoCD is a control loop. It’s a perpetually anxious robot that constantly compares what you said should be running in your Kubernetes cluster (your Git repository’s manifests) with what is actually running. The moment it spots a difference, it has a minor existential crisis and tries to fix it. This is GitOps. The three main components are how this robot’s brain is organized.
The API Server: Your Charming, Well-Dressed Front Desk
Think of the API Server as the friendly, presentable face of ArgoCD. This is the component you interact with 99% of the time, either through the argocd CLI (which talks to it) or the gorgeous web UI. It’s built with gRPC and HTTP/JSON, because the designers knew we’d all want to script this eventually.
Its job is to be a glorified, highly competent receptionist. It handles authentication, authorization, and user input. When you run argocd app create, you’re not talking to the cluster directly; you’re giving your instructions to the API Server. It validates them, makes sure you’re allowed to do this, and then stores the desired state of your application—the source repo, the path, the destination cluster, sync policy—in Kubernetes itself, as a custom resource (an Application CRD).
You can see this for yourself. Create an app and then go peek at its definition:
argocd app create my-awesome-app \
--repo https://github.com/your/repo.git \
--path manifests \
--dest-server https://kubernetes.default.svc \
--dest-namespace default
# Now go look at what was actually created in the cluster:
kubectl get app my-awesome-app -n argocd -o yaml
You’ll see all your settings neatly stored there. The API Server’s other crucial job is serving the live manifest diff in the UI and CLI. It fetches the live state from the cluster and the desired state from the Repo Server and shows you the difference. It’s all talk and no sync, which is honestly a great quality in a receptionist.
The Repo Server: The Librarian with a Serious Caching Problem
You’ve told the API Server where your manifests are (the Git repo). The Repo Server is the component that actually goes and gets them. It’s responsible for cloning your Git repository, checking out the correct revision (commit, tag, branch), and—this is the critical part—rendering your manifests.
Because let’s be honest, you’re not writing raw Kubernetes YAML. You’re probably using Kustomize, Helm, or some other templating system that generates YAML. The Repo Server is the engine that does that generation. It has a dedicated pod that knows how to run helm template or kustomize build.
And here’s the first “questionable choice” you should be aware of: the Repo Server does not have a generic, secure way to pull your private Helm chart repositories. It needs credentials. And how do you give it those credentials? You mount them as a volume or environment variable from a Kubernetes Secret onto the Repo Server pod itself. This is a bit clunky and means any credential added is available to every application managed by ArgoCD. It’s a shared library, and everyone has the same key to the rare books section.
A common pitfall? The Repo Server timing out. If your Helm chart has many dependencies or your Kustomize base is massive, the generate step can take longer than the default timeout (trust me, I’ve been there). You’ll see ComparisonError in the UI. The fix is to bump the --repo-server-timeout-seconds flag on the Repo Server deployment.
# A snippet from the Repo Server deployment patch often needed for larger Helm charts
spec:
template:
spec:
containers:
- name: repo-server
args:
- --repo-server-timeout-seconds=600
The Application Controller: The Muscle That Actually Does the Work
This is the heart of the operation. The Application Controller is the perpetually anxious robot I mentioned. Its job is to:
- Wake up.
- List all the
Applicationresources from the Kubernetes API. - For each app, ask the Repo Server for the desired manifests.
- Fetch the live state from the target cluster.
- Compare the two.
- If they differ and automatic sync is enabled (or if it’s been told to sync by the API Server), calculate the operations needed to make the live state match the desired state.
kubectl applythose operations to the target cluster.- Go back to step 1.
It’s a continuous, relentless loop. This component is where all the actual synchronization logic lives: hooks, sync waves, pruning, diffing strategies. It’s also the bouncer that enforces things like SyncWindows—“nope, not deploying to production at 2 PM on a Friday, I like my weekends quiet.”
The controller is ruthlessly efficient. It uses the Kubernetes API directly, which means it needs RBAC permissions to manage resources in all the namespaces and clusters you target. The key insight here is that the controller’s permissions are your ArgoCD’s permissions. If it can’t do something, neither can you. This is why its service account is bound to a powerful ClusterRole—a fact that rightly makes security folks nervous. You must secure access to ArgoCD itself because it’s a giant “make me a sandwich” button for your entire infrastructure.
The beautiful part of this architecture is its decoupling. The API Server can be down, and the controller will still happily sync your already-defined applications. The Repo Server can be down, and the API Server can still show you the last known state of the world. It’s a system built by people who clearly expected things to fail, and that’s the highest compliment I can give a piece of software.