Skip to content
Stribog

GitOps

All writing

Multi-Cluster GitOps at 100+ Clusters: Argo CD's Limits vs Flux

Multi-cluster GitOps past 20 clusters: where Argo CD's hub saturates, where Flux shifts that cost onto your Git origin, and the load model to find your ceiling.

Stribog24 min readUpdated 28 Jul 2026

Most teams adopt GitOps for the right reasons — auditability, velocity, drift detection — and most teams choose Argo CD for the wrong one: it has a nicer UI. That is not a criticism of Argo CD. It is an observation about how architectural decisions get made under deadline pressure. The UI is real, the onboarding is smooth, and the ApplicationSet API is genuinely elegant for templating deployments across a handful of clusters. The problem surfaces eighteen months later, when the cluster count crosses twenty and the management cluster starts looking like the thing you should have designed against from the start.

This article is not a feature shootout. It is a delivery-topology analysis. The question is not which GitOps tool has better support for Helm or a richer UI — that debate is exhausted. The question is: what does your delivery topology look like at 100 clusters, and which architectural model survives the fault modes you will actually encounter? Hub-and-spoke versus distributed pull is a structural choice with real blast-radius implications, and most teams do not frame it that way until they are already paying the cost.

What follows is a principal-engineer-level walkthrough: Argo CD's scaling ceiling, Flux's sovereign-per-cluster model, the air-gapped and edge cases that make hub-and-spoke structurally untenable, multi-tenancy isolation, and a migration path if you need to move between them — or run both. The goal is to give you the topology decision framework before you need it, not after.

The GitOps Maturity Curve: Why Most Teams Hit a Wall at 20 Clusters

GitOps adoption is real and widespread — CNCF’s Annual Cloud Native Survey treats extensive GitOps use as a maturity signal (for example, 58% of “cloud native innovators” versus 23% of “adopters” report using GitOps principles extensively). But adoption numbers hide a distribution: most of that adoption is in the 1-to-10 cluster range, where a single Argo CD instance on a management cluster is genuinely sufficient and the operational overhead is low. The wall appears around 15 to 25 clusters, and it is not dramatic. It is a slow accumulation of symptoms.

The symptoms look like this: reconciliation latency climbs because the Argo CD application controller is processing a growing queue of sync operations across all spoke clusters. The management cluster's API server starts seeing elevated load from the Argo CD controllers polling the target clusters. ApplicationSet generators — particularly cluster generators backed by a secret-per-cluster pattern — begin taking noticeable time to evaluate. A network partition between the management cluster and one region means Argo CD cannot reconcile clusters in that region, even if the clusters themselves and their Git sources are fully healthy. The management cluster becomes the thing you are most afraid to touch.

Flux users encounter a different wall — not a performance ceiling but an observability gap. When every cluster pulls its own state, you gain resilience and lose the single pane of glass. The Flux Kustomization and GitRepository objects are cluster-local; aggregating status across 100 clusters requires deliberate tooling — a pull-based aggregation layer like Prometheus with federation, or a dedicated platform dashboard. Teams that skip it discover that distributed resilience without distributed observability is just distributed confusion.

Hub-and-Spoke vs. Distributed Pull: The Architectural Difference That Actually Matters

The essential difference is where reconciliation authority lives. In the Argo CD hub-and-spoke model, the management cluster's application controller is the reconciliation authority for all spoke clusters. It watches the Git source, computes the desired state, diffs it against live state, and issues apply operations outward to the spokes. The spokes are passive recipients of decisions made centrally.

In the Flux distributed pull model, each cluster runs its own Flux controllers — source-controller, kustomize-controller, helm-controller — and each cluster independently polls its configured sources and reconciles itself. There is no central authority issuing apply commands. A cluster in Singapore pulls from its configured Git repository or OCI registry on its own schedule, applies its own Kustomizations, and reports status into its own Kustomization and HelmRelease CRDs. It does not know or care what the cluster in Frankfurt is doing.

Delivery topology and blast-radius comparison. The critical difference is not features — it is where reconciliation authority lives and what fails when a component goes down.

This architectural difference has direct blast-radius implications. In the hub-and-spoke model, the failure modes that matter are: management cluster unavailability (reconciliation stops for all spokes), management cluster API server overload (reconciliation becomes degraded for all spokes), and network partition between management and a region (clusters in that region cannot be reconciled). In the distributed pull model, the worst-case scenario for a single cluster failure is exactly that: one cluster stops reconciling. Other clusters are unaffected. The Git repository becoming unavailable stops all clusters — but that is a shared dependency you can mitigate with OCI-based delivery or local mirrors, as discussed in the air-gapped section.

The framing that helps: Argo CD gives you centralized control at the cost of centralized failure modes. Flux gives you distributed resilience at the cost of distributed observability. Neither is wrong; they suit different fleets. The mistake is adopting one without understanding the trade-off.

Argo CD at Scale: ApplicationSets, App-of-Apps, and Where the Bottlenecks Are

Argo CD has two primary patterns for multi-cluster delivery: the app-of-apps pattern and the ApplicationSet API. App-of-apps is the older pattern — a root Application that points to a directory of Application manifests, which Argo CD recursively reconciles. It works, it is predictable, and it is manual: adding a cluster means adding an Application manifest to the root. ApplicationSets are the modern answer: a controller that generates Application objects dynamically from generators including cluster lists, Git directories, pull-request refs, and matrix combinations.

yaml
apiVersion: argoproj.io/v1alpha1
kind: ApplicationSet
metadata:
  name: platform-baseline
  namespace: argocd
spec:
  goTemplate: true
  goTemplateOptions: ["missingkey=error"]
  generators:
    - clusters:
        selector:
          matchLabels:
            env: production
        values:
          # Slashed annotation keys need index, not bracket notation.
          revision: '{{ index .metadata.annotations "gitops/revision" }}'
  template:
    metadata:
      name: 'platform-baseline-{{.name}}'
    spec:
      project: platform
      source:
        repoURL: https://github.com/your-org/gitops-config
        targetRevision: '{{.values.revision}}'
        path: 'clusters/{{ index .metadata.annotations "cluster/region" }}/baseline'
      destination:
        server: '{{.server}}'
        namespace: platform-system
      syncPolicy:
        automated:
          prune: true
          selfHeal: true
        retry:
          limit: 3
          backoff:
            duration: 30s
            factor: 2
            maxDuration: 5m
ApplicationSet with cluster generator — generates one Application per registered cluster secret in the argocd namespace. The cluster generator reads those Secrets from Argo CD's controller namespace; allowing ApplicationSets outside that namespace is an install-time setting (`--applicationset-namespaces`), not a field on this object.

The cluster generator pattern is clean at ten clusters. At 100, the application controller manages 100 Application objects to diff and sync; the work queue grows proportionally. Argo CD has supported replica sharding via ARGOCD_CONTROLLER_REPLICAS since v1.8; selecting the distribution algorithm with controller.sharding.algorithm in argocd-cmd-params-cm (or the ARGOCD_CONTROLLER_SHARDING_ALGORITHM env var / --sharding-method) requires v2.8 or later. Scale the argocd-application-controller StatefulSet and set ARGOCD_CONTROLLER_REPLICAS to match to distribute cluster ownership across them — opt-in, with added operational complexity. The default single-replica deployment will saturate before you hit 100 clusters at any meaningful sync frequency.

The other scaling concern is management-cluster API server load from Argo CD's informers. The application controller establishes watches against every target cluster's API server to detect drift. At 100 clusters that is 100 watcher sets, each issuing list-and-watch calls. Informer cache memory grows linearly with cluster count. Large Argo CD installs report the management-cluster API server as a primary concern by 50 clusters.

The real operational cost at scale is not raw performance — modern clusters can absorb significant reconciliation load. The cost is the operational surface of the management cluster: HA capacity for the sharded controller, network reach to all spokes, and absolute dependency for GitOps delivery. When the management cluster has an incident, fleet-wide GitOps delivery degrades — that is the blast radius you accept.

Flux's Distributed Model: Why Every Cluster Pulls Its Own State

Flux's design principle is that each cluster is a sovereign reconciliation unit. The Flux controller suite — source-controller, kustomize-controller, helm-controller, notification-controller, and image-automation-controller — runs inside the target cluster, not in a separate management cluster. Each cluster independently polls its configured sources and reconciles itself against the desired state in Git or OCI.

yaml
# Source: what to pull and from where
apiVersion: source.toolkit.fluxcd.io/v1
kind: GitRepository
metadata:
  name: gitops-config
  namespace: flux-system
spec:
  interval: 1m
  url: https://github.com/your-org/gitops-config
  ref:
    branch: main
  secretRef:
    name: github-deploy-key
---
# Reconciler: what to apply from the source
apiVersion: kustomize.toolkit.fluxcd.io/v1
kind: Kustomization
metadata:
  name: platform-baseline
  namespace: flux-system
spec:
  interval: 10m
  # force: false (default) — set true only so the controller can delete/recreate a
  # resource when a patch fails on an immutable field; causes downtime. Prefer the
  # per-resource annotation kustomize.toolkit.fluxcd.io/force: enabled.
  force: false
  prune: true
  sourceRef:
    kind: GitRepository
    name: gitops-config
  path: "./clusters/eu-west-production/baseline"
  healthChecks:
    - apiVersion: apps/v1
      kind: Deployment
      name: cert-manager
      namespace: cert-manager
  timeout: 5m
Flux GitRepository and Kustomization — the two core primitives. GitRepository defines the source; Kustomization defines what to apply from it and at what interval. These objects live in the target cluster itself, making the cluster self-sufficient.

The structural advantage at scale is straightforward: there is no central dependency. A cluster in a disconnected edge location reconciles itself using its locally configured source — a Git mirror or an OCI registry — without needing to reach the management cluster that issued the original deployment decision. A cluster that cannot reach its source simply retries at the configured interval; it does not take down reconciliation for other clusters.

The Flux OCIRepository source type, introduced in Flux 2.0, pushes the distributed model further. Instead of every cluster pulling directly from Git — with the associated SSH key management and Git server load at scale — you push Kubernetes manifests as OCI artifacts to a registry, and clusters pull OCI layers. This collapses the source of truth into the same registry infrastructure you are already operating for container images, reduces Git server load dramatically at large cluster counts, and enables the air-gapped patterns discussed in the next section.

End-to-end delivery pipeline showing where policy enforcement integrates for both Argo CD and Flux delivery models. Kyverno enforces at admission regardless of which tool issued the sync.

The sovereignty argument here is not rhetorical. When each cluster carries its own reconciliation controllers, you own the delivery path end-to-end. There is no vendor-operated control plane between your Git repository and your cluster state. Each cluster's source of truth is a Git repository or OCI registry you control — consistent with the capabilities this practice brings to infrastructure delivery and with the long-game principle that systems should be built to outlast any single dependency.

Sizing the Bottleneck: A Load Model You Can Run on Your Own Fleet

Every comparison of these two tools — including, until this section, this one — eventually says something like "teams report the management cluster becoming a concern around fifty clusters." That is folklore. It may even be true for the team that reported it, but it is useless to you, because the number depends entirely on how many Applications each cluster runs, how many resources each Application manages, and how often you reconcile. A fleet of 200 clusters running three Applications each is a smaller problem than 40 clusters running eighty. What follows is the arithmetic instead of the anecdote, so you can compute your own threshold rather than inherit somebody else's.

Take C clusters, A Applications per cluster, K managed resources per Application, and a reconciliation interval R in seconds. Argo CD's default timeout.reconciliation is 180s. The hub's steady-state work is then simply the fleet's total managed state divided by the interval:

text
Application reconciles/sec  =  (C × A) / R
Resource comparisons/sec    =  (C × A × K) / R
Objects held in informer cache =  C × A × K

Worked example — A=20 Applications, K=25 resources, R=180s (Argo CD default):

  clusters │ app-reconciles/s │ resource cmp/s │ cached objects │ ~cache @4KB
  ─────────┼──────────────────┼────────────────┼────────────────┼────────────
        10 │              1.1 │           27.8 │          5,000 │      ~20 MB
        50 │              5.6 │          138.9 │         25,000 │     ~100 MB
       100 │             11.1 │          277.8 │         50,000 │     ~200 MB
       250 │             27.8 │          694.4 │        125,000 │     ~500 MB
The two numbers that decide whether a hub-and-spoke topology survives. Both scale linearly with cluster count, which is why the model breaks predictably rather than suddenly.

The cache column is the one that ends arguments. Those figures are the *steady-state resident set of a single controller replica* before any churn, and they are the reason the failure mode is memory pressure and watch-reestablishment storms rather than a clean CPU ceiling. Sharding divides this: with four controller replicas and ARGOCD_CONTROLLER_REPLICAS=4 (optionally set the sharding algorithm on v2.8+), each shard owns roughly a quarter of the clusters and therefore a quarter of the cache and reconcile load. What sharding does not change is the aggregate watch traffic arriving at your spoke API servers — that total is a property of the fleet, not of the hub's replica count. Sharding relocates the bottleneck; it does not delete it.

Now run the same exercise for Flux, and the interesting result is that the cluster-count term disappears from the per-cluster load entirely. Each cluster reconciles only its own state, so its work is A / R regardless of whether the fleet has ten members or a thousand. This is the distributed model's actual advantage, and it is why the answer to "how many clusters can Flux manage" is that the question does not parse — there is no shared reconciler to saturate.

But the load does not vanish; it moves, and it lands somewhere most comparisons never look. Every cluster independently polls the source of truth, so the Git origin absorbs the fleet-wide traffic that Argo CD's hub used to absorb:

text
Source fetches/sec  =  C / I        (I = GitRepository/OCIRepository interval, seconds)

  clusters │  I=60s  │ I=300s  │  what the Git origin sees
  ─────────┼─────────┼─────────┼──────────────────────────────
        10 │  0.17/s │  0.03/s │  unnoticeable
       100 │  1.67/s │  0.33/s │  a steady, permanent background load
       500 │  8.33/s │  1.67/s │  self-hosted Forgejo/GitLab needs planning
     1,000 │ 16.67/s │  3.33/s │  clone cost now dominates; move to OCI
Flux's real scaling constraint. The hub disappears, but the Git server inherits a request rate proportional to fleet size — the failure that surprises teams migrating away from Argo CD for scale reasons.

This is the trade the tooling debate usually obscures. Argo CD's ceiling is the hub you operate; Flux's ceiling is the source you serve. Neither model removes the fleet-wide cost — they choose which component pays it. That reframing matters because the two bottlenecks fail differently: a saturated hub degrades delivery for every cluster at once, while a saturated Git origin degrades reconciliation *freshness* everywhere but leaves each cluster running its last-known-good state. For a regulated fleet, the second failure mode is the survivable one, and that — not raw throughput — is the substantive argument for the distributed model.

It also explains why OCIRepository matters more than it first appears. Pushing manifests as OCI artifacts moves that C / I request rate onto registry infrastructure built for exactly this access pattern, with pull-through caches and replication you already operate for images — the same registry tier discussed in the self-hosted registry and supply-chain work. If you take one operational decision from this article, make it that one: at three-digit cluster counts, serving reconciliation from a registry rather than a Git endpoint is the change that buys the most headroom for the least work.

Air-Gapped and Edge Clusters: The Connectivity Assumption That Changes Everything

The hub-and-spoke model has a hard architectural dependency: the management cluster must be able to reach the spoke clusters' API servers. For on-premises clusters in the same data center, this is usually fine. For edge clusters — factory floors, retail locations, telecom edge nodes, hospital branch sites — the connectivity assumption breaks down. These clusters operate on intermittent or constrained connectivity, may be in private networks with no inbound path, and must continue running correctly when the upstream network is unavailable.

Argo CD's push-based model cannot reconcile a cluster it cannot reach. When the network is down, Argo CD marks the cluster as unknown and stops applying changes. When the network returns, it applies the accumulated diff — which may be large if the outage was long. This is workable for clusters that are occasionally disconnected, but it is a structural problem for clusters that are normally disconnected and only occasionally connected. Edge computing and regulated on-premises environments increasingly fall into this category.

Flux's pull model handles this naturally. A disconnected cluster retries pulling from its source at the configured interval. If the source is a local mirror — a Git mirror or a Harbor registry running inside the air-gapped network — the cluster reconciles continuously regardless of the upstream network state. Connectivity to the outside world is only needed to update the local mirrors, which can be scheduled as infrequent batch operations. This is the architecture that makes Flux the structural winner for regulated and edge environments.

Flux in a disconnected environment. The cluster reconciles continuously from local mirrors. Upstream updates flow in on a scheduled sync window; status flows out on reconnect.
yaml
apiVersion: source.toolkit.fluxcd.io/v1
kind: OCIRepository
metadata:
  name: platform-manifests
  namespace: flux-system
spec:
  interval: 5m
  # Harbor mirror — no public internet required from the cluster
  url: oci://harbor.internal.corp/gitops/platform-manifests
  ref:
    semver: ">=1.0.0 <2.0.0"
  secretRef:
    name: harbor-pull-secret
  # Verify OCI artifact signature — Cosign key-based (public key in secretRef; keyless omits secretRef and needs Rekor)
  verify:
    provider: cosign
    secretRef:
      name: cosign-pub-key
---
# Kustomization consuming the OCI source
apiVersion: kustomize.toolkit.fluxcd.io/v1
kind: Kustomization
metadata:
  name: platform-edge
  namespace: flux-system
spec:
  interval: 5m
  prune: true
  sourceRef:
    kind: OCIRepository
    name: platform-manifests
  path: "./edge/baseline"
  # Retry aggressively — connectivity may be intermittent
  retryInterval: 30s
  timeout: 3m
Air-gapped Flux configuration — OCIRepository pointed at a local Harbor instance. The cluster never reaches the public internet; Harbor's replication rule pulls upstream images on a schedule. Image pull secrets and TLS certs for Harbor are pre-provisioned in the cluster.

The OCI verification step above is important. In an air-gapped environment where the cluster cannot reach external Rekor transparency logs for keyless Sigstore verification, you use a key-based cosign verification against a public key pre-provisioned in the cluster. This keeps the supply chain integrity guarantee — the cluster will only apply manifests that were signed by the known key — without requiring external network connectivity at reconciliation time. The signing itself happens upstream in CI; bringing CI execution in-house with self-hosted Kubernetes runners means the signing key never leaves your own infrastructure. This is the open-source-as-method principle applied to supply chain integrity: the verification tooling is open, auditable, and operates without calling home.

Multi-Tenancy: How Each Tool Models Team Isolation at the Platform Layer

Multi-cluster GitOps and multi-tenancy are related but distinct problems. Multi-cluster is about fleet topology. Multi-tenancy is about how you isolate teams, permissions, and delivery paths within and across that fleet. Both tools have multi-tenancy models, and they differ in meaningful ways.

Argo CD models tenancy through Projects — an AppProject defines which source repositories, destination clusters, and destination namespaces a set of Applications can use. Projects also control sync windows (when syncs are allowed) and deployment approval requirements. This model centralizes tenancy policy in the management cluster: a platform team maintains the AppProjects, and application teams create Applications that reference a Project. The centralization is a feature in some organizations and a bottleneck in others. Every tenant change requires a platform team action in the management cluster.

Flux models tenancy through namespace-scoped resources and RBAC. Each tenant's Flux resources — their GitRepository, Kustomization, and HelmRelease objects — live in a dedicated namespace. The GitRepository in tenant-a's namespace can only pull from sources that tenant-a's service account can access, enforced by standard Kubernetes RBAC. The platform team bootstraps the tenant namespace and its Flux source; the tenant team manages their own Kustomizations within it. This model is more Kubernetes-native but requires more initial RBAC design. It also means that a tenant's misconfiguration is naturally scoped to their namespace — it cannot affect other tenants' reconciliation.

At 100 clusters, multi-tenancy isolation cannot depend on human review of every manifest. The admission controller must be the enforcement layer. Both Argo CD and Flux are agnostic to what admission controller you run — Kyverno, OPA Gatekeeper, or native ValidatingAdmissionPolicies in Kubernetes 1.30+ — and the policy corpus should be versioned in Git and delivered via the same GitOps pipeline as the rest of your platform configuration. Policy-as-code delivered by GitOps, enforced at admission: this is the architecture that survives organizational scale.

Migration Paths: Moving from Argo CD to Flux (or Making Them Coexist)

If you are running Argo CD at 30 clusters and want to migrate to Flux, the good news is that you are not migrating application code or Kubernetes manifests — you are migrating the delivery controllers. Your Kustomize directories and Helm charts work with both tools. The migration is a question of bootstrapping Flux controllers onto your clusters, translating Argo CD Application resources to Flux Kustomizations and HelmReleases, and cutting over source-of-truth management.

The recommended approach is a parallel-running coexistence phase rather than a hard cutover. During coexistence, Argo CD continues managing existing Applications while Flux is bootstrapped into a subset of clusters and responsible for a subset of workloads. This lets you validate Flux's reconciliation behavior, build operator familiarity, and iron out source configuration before you deprovision Argo CD Applications.

yaml
# One-time bootstrap per cluster (idempotent — safe to re-run)
# Assumes KUBECONFIG is set to the target cluster
flux bootstrap github \
  --owner=your-org \
  --repository=gitops-fleet-config \
  --branch=main \
  --path=clusters/eu-prod-01 \
  --personal=false \
  --token-auth=false \
  --ssh-key-algorithm=ed25519 \
  --components-extra=image-reflector-controller,image-automation-controller

# After bootstrap, the cluster is self-managing:
# flux-system namespace contains GitRepository + Kustomization
# pointing to clusters/eu-prod-01/ in your fleet repo.
# Drop additional Kustomizations there to onboard workloads.
Flux bootstrap for an existing cluster — flux bootstrap creates the flux-system namespace and its GitRepository/Kustomization pointing to your fleet repository. The --path flag directs Flux to the cluster-specific overlay. Run this per cluster during migration; Argo CD Apps can remain in parallel until you are confident.

The coexistence period exposes the real operational question: how do you prevent Argo CD and Flux from fighting over the same resources? The answer is namespace partitioning and label selectors. Argo CD Applications should own resources in namespaces not managed by Flux Kustomizations, and vice versa. Argo CD AppProject spec.destinations (namespace + server allowlist) and Flux's spec.targetNamespace give you the control points. A hard rule to enforce during coexistence: never let both tools own a resource in the same namespace. The resulting conflict — both controllers attempting to reconcile the same object — produces drift oscillation that is annoying to diagnose and guaranteed to happen.

One pattern that works well at intermediate scale (10–30 clusters) is a deliberate hybrid: use Argo CD for workloads where the centralized UI and ApplicationSet templating provides real value — shared platform services, environment promotion gates, workloads that need approval workflows — and use Flux for stateless workloads on edge clusters, air-gapped clusters, and clusters where autonomous reconciliation is the design requirement. This is not a failure to commit to one tool; it is using the right topology for the right fleet segment. The optionality to run both without lock-in to either is itself part of the long-game infrastructure posture.

Choosing a GitOps tool is choosing a blast-radius model. Hub-and-spoke suits a team that wants centralized control and can operate the management cluster with the same rigor as its most critical production service. Distributed pull suits a team that cannot accept a single cluster's problems propagating to the entire fleet.

Blast-Radius Comparison: Failure Modes You Should Design For

A structured blast-radius comparison is more useful than a feature matrix. The failure modes that matter at 100 clusters are not theoretical — they are the scenarios that wake someone up at 2 AM.

  • Management cluster unavailable (Argo CD): Reconciliation stops for all spoke clusters. Clusters continue running their current state, but drift accumulates undetected and no new deployments land until the management cluster recovers. At 100 clusters, even a 30-minute management cluster outage can produce significant drift backlog.
  • Management cluster unavailable (Flux): No impact. Each cluster reconciles independently. The Flux controllers run inside the target clusters; there is no external dependency on a management cluster.
  • Git repository unavailable (both tools): Reconciliation stalls at the GitRepository source level. For Argo CD, all Applications that reference the affected repository stop reconciling. For Flux, all Kustomization objects referencing the affected GitRepository stop; the cluster retries at the configured interval. OCI-based delivery (Flux OCIRepository) decouples from Git availability — clusters pull from the registry, which can be locally mirrored.
  • Single cluster API server degraded (Argo CD): The Argo CD application controller accumulates retry load for the affected cluster. If the cluster's API server is fully unreachable, Argo CD marks it unknown and continues reconciling other clusters — the isolation is reasonable. The problem is that a misbehaving cluster (high latency, intermittent errors) can consume disproportionate controller resources and slow reconciliation for healthy clusters.
  • Single cluster API server degraded (Flux): No cross-cluster impact. The Flux controllers on the affected cluster retry against the local API server. Other clusters are completely unaffected.
  • Network partition between region and management (Argo CD): All clusters in the partitioned region lose reconciliation. Drift accumulates. When the partition heals, Argo CD must reconcile the accumulated diff — a potentially large apply operation that can stress the clusters in the affected region simultaneously.
  • Network partition between region and Git source (Flux, no local mirror): All clusters in the partitioned region stop reconciling new state. Existing state is preserved (prune does not fire on source failure). When connectivity returns, each cluster resumes independently. With a local mirror, the partition is transparent — clusters pull from the mirror continuously.

The pattern is clear: Flux's distributed model isolates failures by design. The blast radius of any single failure — controller, cluster, network segment — is bounded to the affected component and does not propagate. Argo CD's hub-and-spoke model is resilient to spoke failures but fragile at the management layer. This is not a defect in Argo CD's design — it is the inherent trade-off of centralization. The question is whether your operational model can treat the management cluster as a tier-0 service.

Drift Detection and Audit Evidence in a Multi-Cluster GitOps World

GitOps' core value proposition is continuous reconciliation: the cluster's live state is continuously compared against the desired state in Git, and divergences are corrected. This produces a form of audit evidence by construction — if the system is working, the cluster state matches Git. But 'matches Git' is a binary fact, not an audit trail. For regulated environments — and increasingly for any serious production fleet — you need more than 'it was reconciled'; you need 'what was applied, when, by which source revision, and what was the diff.' That is precisely what SOC 2's CC8 change-management criterion asks an auditor to trace — and a signed, reviewed Git commit answers it more directly than a change-ticket system ever does.

Argo CD produces sync history in its application controller and exposes it via the Argo CD API and UI. Each sync operation records the target revision, the resources modified, and the result. This data lives in the management cluster's etcd unless you configure an external audit log sink. Flux produces Event objects for each reconciliation result and populates Kustomization and HelmRelease status conditions with last-applied-revision information. Neither tool produces a compliance-grade audit trail by default — you need to extract events and ship them to a durable external store.

The practical architecture for multi-cluster audit evidence is: configure Flux's notification-controller (or Argo CD's notification engine) to emit reconciliation events to a centralized event store — a Loki cluster, an OpenSearch instance, or a compliance-grade audit log system. Include the source repository URL, the commit SHA, the resources applied, and the reconciliation outcome. Annotate each cluster's reconciliation events with cluster identity metadata so you can filter across the fleet. Combined with Kyverno PolicyReport CRDs — which record which resources failed which policies and when — this gives you a compliance-grade delivery audit trail without additional tooling beyond what you are likely already running.

At fleet scale, drift detection also means detecting configuration skew between clusters — where clusters that should be identically configured have diverged due to partial rollout, manual intervention, or failed reconciliation. This requires fleet-wide diff tooling beyond what either Argo CD or Flux provides natively. Custom aggregation pipelines pulling Kustomization .status.lastAppliedRevision across all clusters give you the visibility you need. The investment is worth making before you need it — discovering that 12 of your 100 clusters are running a different version of your admission webhook during a security audit is not a pleasant discovery.

The Topology Decision Framework: Which Model Fits Your Fleet

The decision between Argo CD hub-and-spoke and Flux distributed pull is not a feature evaluation — it is a topology commitment. Use the following framework to determine which model fits your fleet structure, fault model, and connectivity assumptions.

  • Fleet size under 20 clusters, stable connectivity, team prefers centralized UI: Argo CD hub-and-spoke is the pragmatic choice. Lower operational complexity, excellent ApplicationSet templating, strong UI for deployment oversight. Architect the management cluster as a tier-0 service from day one.
  • Fleet size over 30 clusters, or growing beyond 20: Evaluate Flux's distributed model seriously. The management cluster becomes an increasing operational burden with Argo CD; Flux's per-cluster controllers scale linearly without a central bottleneck.
  • Any edge or air-gapped clusters: Flux's pull model with local OCI/Git mirrors is the structurally correct answer. Argo CD's push model requires connectivity that edge environments cannot guarantee.
  • Hard requirement to bound blast radius to single cluster: Flux. The distributed model makes single-cluster failures structurally isolated.
  • Compliance requirement for continuous reconciliation with audit trail: Both tools support this, but require explicit configuration of event sinks. Budget the engineering time to build the audit aggregation pipeline.
  • Multi-tenancy with strong namespace isolation: Flux's RBAC-native tenancy model pairs well with Kyverno's namespace-scoped policy enforcement. Argo CD's Projects model is easier to visualize but requires more platform team involvement per tenant change.

The long-game principle applies directly here: the topology you choose at 20 clusters is the topology you will operate at 100, unless you deliberately migrate. The architectural debt of adopting hub-and-spoke for convenience and then discovering its blast-radius implications at scale is real and costly to unwind. Making the topology decision explicitly — with the fault model in front of you — is upfront investment that pays back over years of fleet operation.

If you are building or scaling a multi-cluster fleet and want an independent assessment of your delivery topology, blast-radius model, and GitOps architecture before committing to a direction, that is the kind of engagement we do.

§FAQ/Common questions

Frequently asked

Does Flux actually scale better than Argo CD, or does the load just move?

It moves. Flux removes the shared reconciler, so per-cluster work is `A / R` regardless of fleet size — but every cluster then polls the source independently, putting `C / I` requests per second on your Git origin. At 500 clusters on a 60s interval that is ~8.3 fetches/sec against your Forgejo or GitLab instance, permanently. Argo CD's ceiling is the hub you operate; Flux's ceiling is the source you serve. The reason to prefer the distributed model is not raw throughput but the failure mode: a saturated hub degrades delivery fleet-wide at once, while a saturated origin only degrades reconciliation freshness and leaves every cluster running its last-known-good state.

How do I stop Flux's Git server load from becoming the new bottleneck?

Serve reconciliation from an OCI registry instead of a Git endpoint. Flux's `OCIRepository` source moves the same `C / I` request rate onto registry infrastructure designed for exactly this access pattern, with pull-through caching and replication you already run for container images. Failing that, raise the interval (a 300s interval cuts origin load fivefold and is acceptable for most platform baselines), and make sure clones are shallow. At three-digit cluster counts the OCI switch buys the most headroom for the least work.

Can Argo CD and Flux run on the same cluster simultaneously?

Yes, but with strict namespace partitioning. Both tools reconcile Kubernetes resources; if both own resources in the same namespace, they will fight over the live state and produce drift oscillation. During migration, partition by namespace: Argo CD owns workloads in namespaces it created, Flux owns workloads in namespaces it manages. Never let both controllers own the same object.

What is the practical cluster count limit for Argo CD without controller sharding?

There is no fixed number, and any article quoting one is quoting folklore. The load is `(C × A) / R` reconciles per second against a cache of `C × A × K` objects, so the ceiling moves with your Applications-per-cluster (A) and resources-per-Application (K), not with cluster count alone. At A=20, K=25 and the 180s default, 100 clusters means ~11 reconciles/sec over ~50,000 cached objects — roughly 200 MB resident per replica before churn. Thin Applications push that ceiling far higher; fat platform baselines pull it far lower. Compute it for your fleet, then scale the `argocd-application-controller` StatefulSet and set `ARGOCD_CONTROLLER_REPLICAS` to match when a replica's cache stops fitting comfortably in memory — sharding assigns *clusters* across replicas, not individual Applications. Optionally select a distribution algorithm with `controller.sharding.algorithm` / `ARGOCD_CONTROLLER_SHARDING_ALGORITHM` on v2.8+. Sharding divides per-replica load but leaves aggregate watch traffic to spoke API servers unchanged.

How does Flux handle a cluster that is offline for several days and then reconnects?

Flux retries the pull at the configured interval indefinitely. On reconnect, the source-controller fetches the latest state from Git or OCI, the kustomize-controller computes the full diff against current live state, and applies the accumulated changes. This is a normal reconciliation — the cluster catches up without any manual intervention. If prune is enabled, resources that should have been removed during the offline period will be pruned on reconnect.

How do I aggregate status across 100 Flux-managed clusters?

Flux does not include a native multi-cluster dashboard. The common patterns are: (1) use Flux's notification-controller to push events to a central store (Loki, OpenSearch) and build a Grafana dashboard; (2) use a Prometheus federation setup where each cluster exposes Flux metrics and a central Prometheus scrapes them; (3) run a periodic script that iterates over kubeconfigs and collects Kustomization status. There is no upstream multi-cluster status API in Flux today; aggregation remains an operator-built layer using the patterns above.

Does Flux support progressive delivery (canary, blue/green) natively?

Not natively. Flux integrates with Flagger — an OSS progressive delivery controller — which adds `Canary` CRDs and manages traffic splitting via service mesh or ingress controllers. The Flux + Flagger combination gives you canary, blue/green, and A/B delivery patterns. Argo CD has tighter native integration with Argo Rollouts for the same use case. If progressive delivery is a primary requirement, Argo CD's native Rollouts integration may be the deciding factor.

What is the right Flux reconciliation interval for a production fleet?

For platform-baseline Kustomizations (cluster infrastructure, admission controllers, network policies): 5-10 minutes, with `retryInterval: 30s`. For application workloads: 1-5 minutes if you need fast drift detection; 10-15 minutes if API server load is a concern. Set `timeout` conservatively — a stalled reconciliation should fail fast rather than hold a lock. For air-gapped clusters pulling from local mirrors: 1-2 minutes is reasonable since the source is low-latency and local.

multi-cluster GitOps Kubernetes ArgoCD Flux scaleArgoCD vs FluxCD 100 clustersGitOps multi-cluster enterprise 2026Flux CD distributed model productionArgoCD ApplicationSet scale limitsGitOps air-gapped clusters

Executive Briefing

Thirty minutes to clarify your infrastructure risk

Walk us through your vendor footprint and regulatory constraints. We will tell you honestly where sovereignty creates leverage — and where it does not. No pitch deck. No obligation.