Keeping ArgoCD fast as applications grow: repo optimization with shallow clones and caching, application controller tuning, API server optimization, large-scale patterns, and cluster health.

In episode 24 we built a compliance engine from Git and ArgoCD logs. But there's an unavoidable tension: the more complete the evidence and the more applications, the heavier ArgoCD's load. Large Git repos cloned repeatedly, thousands of applications reconciled continuously, and a slow API server make operations feel stuck. In this episode we discuss performance tuning & optimization — keeping ArgoCD lean as scale grows.
Why does this matter? ArgoCD is a controller, and controllers have a cost per unit of work: each application needs a Git-vs-cluster comparison. Without optimization, that cost grows linearly and eventually becomes a real problem — slow syncs, sluggish UI, OOM'd controllers. This episode provides a framework for optimizing where it has the most impact: the repo, the controller, the API server, and architectural patterns.
The repo is the source of everything, and cloning is ArgoCD's most expensive operation. The repo server clones every repo when a change is detected — a big repo means waiting.
The repo server supports shallow clone (--depth 1), which drastically reduces cloning time for large repos:
args:
- /usr/local/bin/argocd-repo-server
- --depth=1In addition, ArgoCD stores manifest caches in Redis. After the first clone, kubectl apply for the same resources uses the cache. For repos that rarely change, the cache makes reconciliation much faster than re-cloning every time.
The repo server opens HTTP connections to GitHub/GitLab. Make sure the connection count is sufficient via ARGOCD_GIT_CONCURRENCY (default 5) and ARGOCD_GIT_SHALLOW_CLONE=true on the deployment env. For the cache:
argocd app get api --refresh or the UI's Hard Refresh forces a re-clone and re-render of manifests. This resolves cases where Git changed but ArgoCD still holds the old cache.repositories.* setting; tune the trade-off between freshness and repo server load.argocd_repoclientset_processors_run_count
argocd_repo_pending_request_total
argocd_repo_requests_totalIf pending_request_total stays high, the repo server is overwhelmed — scale up replicas (episode 26) or increase ARGOCD_GIT_CONCURRENCY.
The application controller is the heart of reconciliation: it compares Git and cluster for every application, then runs operations. The two most influential knobs:
--status-processors (default 20) — the number of goroutines processing status comparisons. Increase it when there are many applications: --status-processors=40.--operation-processors (default 10) — the number of workers for sync operations. Increase when many syncs run concurrently.metadata:
annotations:
argocd.argoproj.io/reconcile-period: 10m
spec:
...Note the trade-off: a longer interval reduces load but slows drift detection. For critical production environments, keep 3 minutes; for batch or non-critical applications, 10-30 minutes is safe.
A controller running out of memory is the most common failure at mid-scale. Give it realistic resource limits and observe the usage:
spec:
template:
spec:
containers:
- name: argocd-application-controller
resources:
requests:
cpu: 500m
memory: 1Gi
limits:
memory: 2GiMonitoring metrics (episode 22) — argocd_app_reconcile_count and memory usage — determine whether to raise the limits, raise the workers, or switch to the sharding patterns below.
The API server serves the UI, CLI, and webhooks. With many active users/CI, it can become a bottleneck:
argocd-server arguments to limit connections and give slow requests a timeout.limit-rpm, or an API gateway) so one client can't flood it.metadata:
annotations:
nginx.ingress.kubernetes.io/limit-rpm: "60"
nginx.ingress.kubernetes.io/proxy-read-timeout: "120"
spec:
...When thousands of applications can't be handled by a single controller, there are three patterns:
argocd.argoproj.io/shard). The easiest, no extra clusters.Label-based sharding is the cheapest first step:
args:
- --application-shard=0
- --sharding-method=legacyOr use modern label-based sharding: give the controller StatefulSet the argocd.argoproj.io/shard label and the same label to the Applications. ArgoCD v2.15+ supports this seamlessly. Meanwhile, ApplicationSet (episode 11) helps keep thousands of applications consistent even when the load is spread.
ArgoCD also depends on the health of the cluster itself. Three basic practices:
argocd-repo-server and argocd-server can be HPA'd on CPU; the controller is better scaled vertically or via sharding because it's stateful.apiVersion: autoscaling/v2
kind: HorizontalPodAutoscaler
metadata:
name: argocd-repo-server
namespace: argocd
spec:
scaleTargetRef:
apiVersion: apps/v1
kind: Deployment
name: argocd-repo-server
minReplicas: 1
maxReplicas: 5
metrics:
- type: Resource
resource:
name: cpu
target:
type: Utilization
averageUtilization: 70Warning
Optimization without measurement is a guess. Before changing any knob, record a baseline: what's reconcile_count per minute, how much controller memory, what's pending_request on the repo server. Change one variable, measure again, then evaluate. This approach prevents "optimizations" that actually make things worse.
This episode unpacked ArgoCD performance layer by layer: repo optimization with shallow clones and caching, application controller tuning with status processors and reconcile intervals, API server optimization with rate limits and Redis, large-scale patterns with sharding, multi-instance, and federation, and cluster health with quotas, HPA, and node affinity.
The points you should take with you:
--status-processors and --operation-processors are the controller's main knobs.A fast ArgoCD can still be a single point of failure — fast doesn't mean resilient. In the next episode 26 we discuss high availability setup — HA architecture, per-component HA, Redis with Sentinel, network HA, and failure testing with chaos. See you in episode 26!