Argo CD vs Flux: The GitOps Comparison After You Actually Operate Them

If you have been following the GitOps conversation on this blog, you have already seen one half of it: the OpenTofu + Argo CD post walks through bootstrapping k3s and handing day-one operations to Argo CD, and the Compose vs Kubernetes post settles when a cluster earns its keep at all.

What neither post answered is the question everyone asks the moment the cluster is real: Argo CD or Flux? I have now operated both long enough that the feature tables stopped being useful, because the tables are written by people who read the docs, and the differences only show up after you have run them for a while.

I cover the bootstrap flow on both sides, how each one really handles Helm, where secrets get decrypted, how multi-cluster actually works, what drift detection does when you are not looking, the image automation story, disaster recovery with real commands, the UI and API surface, and the repo structure that makes each one click.

The short answer up front: both are CNCF graduated, both reconcile from git, and the choice is mostly about who operates the cluster and how many control-plane components you want to babysit.

The short version

  • Argo CD is a centralized application platform; Flux is a set of distributed controllers. Argo CD runs one control plane (server, repo-server, application-controller, Redis, database) that can manage many clusters. Flux runs per-cluster controllers with no central coordinator, which changes the blast radius and the RBAC story.
  • Bootstrap is the first place the philosophies show. flux bootstrap is one command that creates the repo, installs the controllers, and commits the manifests. Argo CD is a Helm chart, then you wire the app-of-apps handoff by hand.
  • Helm works on both, in different shapes. Argo CD renders charts in the repo-server and tracks the rendered result. Flux manages HelmRelease objects natively with the helm-controller, and v2.9 added post-render strategies and chart hooks support.
  • Secrets stay out of git on both, but decryption lives in different places. Flux decrypts SOPS files natively at apply time, with Age support including the post-quantum cipher in v2.9. Argo CD needs a sidecar plugin or a Kustomize secret generator plus the age key mounted into the repo-server.
  • Multi-cluster is the biggest architectural fork. Argo CD is hub-and-spoke: one control plane, many registered clusters, central RBAC. Flux is distributed: every cluster runs its own controllers and reads its own git paths.
  • Drift handling differs in the default. Argo CD self-heals only when you turn selfHeal on, and it diffs against a watch-backed cache. Flux reconciles on an interval and applies the desired state, with server-side apply and field ignore rules in v2.9.
  • Image automation is a bolt-on on Argo and core on Flux. argocd-image-updater is a separate project you install; Flux ships image-reflector-controller and image-automation-controller in the toolkit.
  • Disaster recovery is the part nobody tests, and the two stories are not the same. Flux recovery is re-running bootstrap against the repo and restoring the flux-system secret. Argo CD recovery means exporting the applications, projects, and secrets the control plane holds, because git alone does not reconstruct the registry.
  • The UI is the real differentiator for teams. Argo CD ships a full web UI with SSO and RBAC. Flux ships no official UI; you live in kubectl, the CLI, and the Kubernetes API, with the Flux Web UI as a separate project.
Verified
  • Argo CD3.5.x (3.5 GA 2026-08-04; latest patch 3.5.2)
  • Fluxv2.9.x (2.9 GA June 2026; latest patch 2.9.4)
  • Argo CD Helm chartargo-cd chart 10.x line
  • argocd-image-updaterSeparate project, current at time of writing

Checked 2026-08-27 against the GitHub releases pages for argoproj/argo-cd and fluxcd/flux2, endoflife.date/argo-cd, and fluxcd.io release notes. Both projects ship fast; re-check versions before you rely on them.

What each tool actually is

Argo CD: a centralized application platform

Argo CD started at Intuit in 2018 and graduated as a CNCF project in December 2022. The architecture is a control plane: the argocd-server exposes the UI and API, the argocd-repo-server fetches git and renders manifests (plain YAML, Kustomize, Helm, Jsonnet), and the argocd-application-controller watches both the git state and the live cluster through a cached informer, then syncs when they drift. The unit of management is the Application: a CR that says which repo, path, revision, and destination cluster to reconcile. A project groups applications and defines RBAC boundaries.

Flux: a toolkit of controllers

Flux v2, the GitOps Toolkit, came out of Weaveworks and also graduated as a CNCF project in 2022. There is no central control plane. Each cluster runs a set of controllers: source-controller pulls git, OCI artifacts, or buckets; kustomize-controller applies Kustomize or plain YAML; helm-controller manages HelmRelease objects; notification-controller handles events and webhooks; image-reflector-controller and image-automation-controller do the image update loop. The API is the Kubernetes API: everything is a CRD, and the flux CLI mostly generates YAML for you.

Bootstrap: one command versus a chart plus wiring

Run both from scratch and the difference is visible in the first ten minutes.

bootstrap a cluster with Flux
$ flux bootstrap github \
    --owner=yourname \
    --repository=gitops \
    --branch=main \
    --path=./clusters/prod
# creates the repo if missing, installs the controllers,
# commits the manifests, and writes the kubeconfig

$ flux get kustomizations
NAME          READY   MESSAGE
flux-system   True    Applied revision: main@sha1:...

What that one command did: it generated the toolkit manifests for the version you are running, installed them into the cluster, created a git repository if it did not exist, and committed the flux-system Kustomization back into git. The cluster now reconciles itself from the repo. There is no separate control plane to patch, and the same command run against a second cluster gives you the distributed model with no central state.

install Argo CD and hand off to app-of-apps
$ helm repo add argo https://argoproj.github.io/argo-helm
$ helm install argocd argo/argo-cd -n argocd --create-namespace

# the full bootstrap handoff is in the OpenTofu + Argo CD post;
# the short version: create the app-of-apps Application in git
$ argocd app create apps \
    --repo https://github.com/yourname/gitops \
    --path apps --dest-server https://kubernetes.default.svc \
    --dest-namespace argocd
$ argocd app sync apps

Argo CD is a platform you install, then configure: a chart, a namespace full of services, a Redis for caching, a database for app state, an admin password you rotate on day one, and a bootstrap Application that points at a directory of further Application manifests. It is more moving parts, and the payoff is the UI, the RBAC model, and the centralized view over many clusters.

Helm: two philosophies

Both tools run Helm charts, but the model differs in a way that bites in production. Flux treats a Helm release as a first-class object. You declare a HelmRelease, the helm-controller does the install, upgrade, rollback, and reconciliation, and the release state is managed the way Helm expects, including the secret-based release history.

apiVersion: helm.toolkit.fluxcd.io/v2
kind: HelmRelease
metadata:
  name: cert-manager
  namespace: cert-manager
spec:
  interval: 30m
  chart:
    spec:
      chart: cert-manager
      version: "1.17.x"
      sourceRef:
        kind: HelmRepository
        name: jetstack
        namespace: flux-system
  values:
    installCRDs: true
  driftDetection:
    mode: warn    # or enabled; field-ignore rules added in v2.9

Argo CD renders the chart inside the repo-server and applies the rendered output as plain manifests tracked by the Application. You point the Application at a chart in a Helm repository or a path in git, set values, and Argo CD syncs the rendered result. The release object itself is not tracked the way helm tracks it; Argo CD compares rendered manifests against the live cluster, which is a different mental model and a common source of surprise when someone runs helm rollback against an Argo-managed chart.

apiVersion: argoproj.io/v1alpha1
kind: Application
metadata:
  name: cert-manager
  namespace: argocd
spec:
  project: default
  source:
    chart: cert-manager
    repoURL: https://charts.jetstack.io
    targetRevision: "1.17.x"
    helm:
      valueFiles:
        - values-prod.yaml
  destination:
    server: https://kubernetes.default.svc
    namespace: cert-manager
  syncPolicy:
    automated:
      prune: true
      selfHeal: true

Flux 2.9 added post-render strategies and chart hooks support, which closes most of the gaps for complex charts, and Argo CD 3.x keeps improving Helm handling too. The practical difference remains: Flux owns the release lifecycle, Argo CD owns rendered manifests. Pick the one that matches how your team already thinks about Helm.

Secrets: SOPS, age, and where decryption happens

The rule on both is the same: secrets do not live in git in plaintext, and the SOPS + age pattern from the Docker secrets post is the baseline that keeps working here. The difference is where decryption happens. Flux decrypts natively: the kustomize-controller reads .sops.yaml and the age key you provide, decrypts at apply time, and never writes plaintext into the repo. Flux v2.9 extended SOPS support to the Age post-quantum cipher.

# on your admin machine, generate an age key
$ age-keygen -o age.agekey

# encrypt a secret file with the public key, commit the ciphertext
$ sops --encrypt --age <public-key> secrets.enc.yaml > secrets.enc.yaml

# give the cluster the key once; Flux decrypts at apply time
$ flux create secret sops-age --age-file=age.agekey
$ kubectl -n flux-system get secret sops-age

Argo CD has no built-in SOPS. The two common paths are the argocd-cmp sidecar plugin, which runs sops during manifest generation, or a Kustomize secret generator that reads an encrypted file with an age key. Either way the age key has to be mounted into the repo-server, and you manage the plugin version yourself.

# Argo CD: SOPS via the cmp plugin, referenced from the Application
spec:
  source:
    plugin:
      name: sops-age

# or via kustomize: a secretGenerator reading an encrypted file
# with an age key mounted into the repo-server from a secret

Multi-cluster: hub-and-spoke versus distributed controllers

This is the fork that decides most real-world choices. Argo CD is hub-and-spoke: one control plane holds the registry of clusters, and the same UI, RBAC, and ApplicationSets span all of them. Flux is distributed: every cluster runs its own controllers, and there is no central credential store that can reach into every cluster at once.

# Argo CD: register clusters on the hub
$ argocd cluster add prod-ctx --name prod
$ argocd cluster list

# Flux: no registration, each cluster bootstraps its own path
$ flux bootstrap github --owner=you --repository=gitops \
    --path=./clusters/prod
$ flux bootstrap github --owner=you --repository=gitops \
    --path=./clusters/dev

The trade-off is the one you expect: Argo CD gives you central control and a single pane of glass, at the cost of a control plane that is a high-value target and a single point of failure for every cluster it manages. Flux gives you isolation, because a compromised Flux cluster exposes only itself, and the cost is that fleet-wide changes are git-wide changes: you edit a path, not a dashboard. For a homelab that is a feature; for a platform team it is a reason to consider Argo CD.

Drift detection: who owns the cluster state

Both tools are designed to make the cluster converge to git. The difference is how aggressively, and what happens when another controller edits a resource. Argo CD keeps a watch-backed cache of live state, diffs it against the desired manifests, and shows you the diff in the UI. Sync applies the changes. selfHeal is off by default; you opt in with syncPolicy.automated.selfHeal: true, and that is when the interesting behavior starts.

Flux reconciles on an interval and applies the desired state, and it does not ask first. Manual kubectl edits are reverted on the next reconciliation, which is the behavior people either love or discover the hard way. Flux 2.9 made this more surgical with server-side apply and field ignore rules, so you can let Flux own most fields while another controller owns specific ones. Argo CD's equivalent is compare-options and ignoreDifferences.

Image automation: update the manifest when the image changes

Keeping the image tag in sync is the feature that separates the tools the most clearly. On Argo CD, image automation is a separate project, argocd-image-updater, that you install next to Argo CD. On Flux, the image-reflector-controller and image-automation-controller are part of the GitOps Toolkit, so the loop is core, not an add-on.

apiVersion: image.toolkit.fluxcd.io/v1beta2
kind: ImagePolicy
metadata:
  name: web
  namespace: apps
spec:
  imageRepositoryRef:
    name: web
  policy:
    semver:
      range: ">=1.0.0 <2.0.0"
---
apiVersion: image.toolkit.fluxcd.io/v1beta1
kind: ImageUpdateAutomation
metadata:
  name: apps
  namespace: flux-system
spec:
  interval: 15m
  sourceRef:
    kind: GitRepository
    name: flux-system
  update:
    strategy: Setters
    path: ./apps

The Flux loop: ImageRepository scans the registry, ImagePolicy applies a rule (semver, regex, or digest), and ImageUpdateAutomation writes the new tag back to git. Argo CD's image updater does the same job through annotations on the Application: it polls the registry, updates the manifest parameter, and can commit back to git or just sync. Both end with a git commit, which is the point: the update goes through the same reconcile loop as everything else.

# Argo CD: image updater annotations on the Application
metadata:
  annotations:
    argocd.argoproj.io/image-update.auto: "true"
    argocd.argoproj.io/image-update.registry-preference: docker.io
    argocd.argoproj.io/image-update.strategy: semver
    argocd.argoproj.io/image-update.semver-range: ">=1.0.0 <2.0.0"

The operational difference: Flux's image automation is maintained with the toolkit and upgraded with it; argocd-image-updater is a separate release train you have to track, which is exactly the kind of dependency the release-tracking workflow exists for.

Disaster recovery: what you actually need to rebuild

Git is the source of truth in both models, so a naive disaster recovery plan is reinstall and re-reconcile. The honest difference is how much state lives outside git. Flux keeps almost nothing in the cluster except the flux-system secret (the age key and git credentials). Argo CD keeps a lot: the Application and Project objects, repo credentials, SSO configuration, the Redis cache, and the database. Some of that can be rebuilt from git, and the app-of-apps pattern is designed so the bootstrap Application is in git, but the registry itself, the secret store, and the RBAC policies do not reconstruct themselves.

disaster recovery, both ways
# Flux: the repo is the backup. Rebuild the cluster, re-bootstrap.
$ flux bootstrap github --owner=you --repository=gitops \
    --path=./clusters/prod
# the flux-system secret must be restored from your backup,
# otherwise the cluster cannot decrypt its own config

# Argo CD: export what the control plane holds, then reinstall
$ argocd export --all > argocd-backup.yaml
$ kubectl -n argocd get applications,projects,secrets -o yaml > argocd-state.yaml
$ helm install argocd argo/argo-cd -n argocd
$ kubectl apply -f argocd-backup.yaml

Test both. The Flux test is a clean VM, re-bootstrap, and watch it converge from git. The Argo CD test is exporting the state, wiping the namespace, and rebuilding. Most teams never run either test, and the backup post has the drill discipline for why that is a mistake.

UI and API: the dashboard versus the kubectl workflow

Argo CD ships a real web UI: application cards, sync buttons, diffs, logs, a settings surface, SSO integration, and RBAC per project. It is the reason many platform teams choose it, and it is genuinely good. The API is a first-class citizen: the argocd CLI talks to it, and everything the UI can do, the API can do, which makes automation scriptable.

Flux ships no official UI. The interfaces are kubectl, the flux CLI, and the Kubernetes API, and everything is a CRD, which means standard Kubernetes tooling, git diff, and GitOps all the way down. The Flux Web UI exists as a separate project and is improving, but it is not part of the default install. For a team that lives in a terminal, Flux is less machinery; for a team that needs a dashboard for non-Kubernetes people, Argo CD is the answer.

Repo structure: app-of-apps versus Kustomization paths

The structure that works on Argo CD is the app-of-apps: one bootstrap Application points at a directory of Application manifests, each of which can be a Helm chart, a Kustomize overlay, or plain YAML. ApplicationSets add generators for multi-cluster or multi-environment from a list or a git directory. The structure that works on Flux is a clusters directory for the platform (generated by bootstrap) plus an apps directory with one Kustomization per application, and environments expressed as separate paths or overlays.

apiVersion: argoproj.io/v1alpha1
kind: Application
metadata:
  name: bootstrap
  namespace: argocd
  finalizers:
    - resources-finalizer.argocd.argoproj.io
spec:
  project: default
  source:
    repoURL: https://github.com/you/gitops
    targetRevision: HEAD
    path: apps
  destination:
    server: https://kubernetes.default.svc
    namespace: argocd
  syncPolicy:
    automated:
      prune: true
      selfHeal: true
apiVersion: kustomize.toolkit.fluxcd.io/v1
kind: Kustomization
metadata:
  name: apps
  namespace: flux-system
spec:
  interval: 10m
  path: ./apps
  prune: true
  sourceRef:
    kind: GitRepository
    name: flux-system
# apps/web/kustomization.yaml holds the app itself

Both end with the same repo shape: gitops/apps/<app>/ with the manifests, and the platform layer at the root. The difference is the object that ties a path to a cluster: an Application that points at a destination, or a Kustomization that runs inside the cluster it lives on. Flux's model is more boring and more uniform; Argo CD's is more explicit about what runs where, which is exactly the trade you would expect.

The limits that bite

  • Argo CD is a platform you operate. Redis, a database, an admin password to rotate, SSO to maintain, and a repo-server to upgrade. The UI hides the machinery; the machinery is still there.
  • Flux has no dashboard by default. If your team wants a GUI, you are building it or running the separate Flux Web UI, which is another dependency.
  • Both assume you own drift. selfHeal and force apply only work when you have told each tool which fields it may touch. Skip the ownership config and they fight your other controllers.
  • Image automation on Argo is a second release train. argocd-image-updater versions, issues, and upgrades are separate from Argo CD itself. Flux's is core, but core also means it moves with the toolkit.
  • Multi-cluster Argo is a bigger attack surface. one control plane with credentials to many clusters is a single high-value target; the distributed model trades that for less central visibility.
  • The UI gap is not closing as fast as people hope. Argo CD's UI stays the strongest argument, and Flux's Web UI remains a separate project with its own roadmap. Test both with your actual team before betting on a roadmap.

At a glance

Argo CD vs Flux at a glance
FeatureArgo CDFlux (GitOps Toolkit)
ArchitectureCentralized control plane: server, repo-server, controller, Redis, DBDistributed controllers per cluster, no central coordinator
BootstrapHelm chart, then app-of-apps handoffflux bootstrap: one command, repo plus cluster committed
HelmRenders charts, tracks rendered manifestsHelmRelease objects managed by helm-controller
SecretsSOPS via cmp plugin or kustomize generatorNative SOPS at apply time, Age plus post-quantum in v2.9
Multi-clusterHub-and-spoke, cluster add, central RBACPer-cluster controllers, path-based, no registry
DriftDiff against watch-backed cache; selfHeal opt-inInterval reconcile, applies desired state; SSA field-ignore in v2.9
Image automationargocd-image-updater, separate projectimage-reflector plus image-automation controllers, core
Disaster recoveryExport applications, projects, secrets; reinstallRe-bootstrap from git; restore flux-system secret
UI and APIFull web UI, REST API, argocd CLIkubectl, flux CLI, Kubernetes API; no official UI
Repo structureApp-of-apps and ApplicationSetsKustomizations pointing at git paths
LicenseApache-2.0Apache-2.0
Best forPlatform teams, many developers, central controlOperators who want less machinery and more isolation
  • Architecture

    Argo CD
    Centralized control plane: server, repo-server, controller, Redis, DB
    Flux (GitOps Toolkit)
    Distributed controllers per cluster, no central coordinator
  • Bootstrap

    Argo CD
    Helm chart, then app-of-apps handoff
    Flux (GitOps Toolkit)
    flux bootstrap: one command, repo plus cluster committed
  • Helm

    Argo CD
    Renders charts, tracks rendered manifests
    Flux (GitOps Toolkit)
    HelmRelease objects managed by helm-controller
  • Secrets

    Argo CD
    SOPS via cmp plugin or kustomize generator
    Flux (GitOps Toolkit)
    Native SOPS at apply time, Age plus post-quantum in v2.9
  • Multi-cluster

    Argo CD
    Hub-and-spoke, cluster add, central RBAC
    Flux (GitOps Toolkit)
    Per-cluster controllers, path-based, no registry
  • Drift

    Argo CD
    Diff against watch-backed cache; selfHeal opt-in
    Flux (GitOps Toolkit)
    Interval reconcile, applies desired state; SSA field-ignore in v2.9
  • Image automation

    Argo CD
    argocd-image-updater, separate project
    Flux (GitOps Toolkit)
    image-reflector plus image-automation controllers, core
  • Disaster recovery

    Argo CD
    Export applications, projects, secrets; reinstall
    Flux (GitOps Toolkit)
    Re-bootstrap from git; restore flux-system secret
  • UI and API

    Argo CD
    Full web UI, REST API, argocd CLI
    Flux (GitOps Toolkit)
    kubectl, flux CLI, Kubernetes API; no official UI
  • Repo structure

    Argo CD
    App-of-apps and ApplicationSets
    Flux (GitOps Toolkit)
    Kustomizations pointing at git paths
  • License

    Argo CD
    Apache-2.0
    Flux (GitOps Toolkit)
    Apache-2.0
  • Best for

    Argo CD
    Platform teams, many developers, central control
    Flux (GitOps Toolkit)
    Operators who want less machinery and more isolation

Which should you pick?

  • Choose Argo CD when you run a platform team, developers need a dashboard and RBAC, you manage many clusters from one place, or you already have the OpenTofu + Argo CD bootstrap in place. The UI is the feature; pay the control-plane cost.
  • Choose Flux when you operate the cluster yourself, you want the fewest components to patch, you value per-cluster isolation, or you want native SOPS and core image automation. The bootstrap is genuinely one command, and the mental model is Kubernetes-native all the way down.
  • Run neither when the cluster is still a single node or the workload fits Compose. The Compose vs Kubernetes verdict applies unchanged: GitOps is a platform tool, and the platform has to earn its keep first.

My honest read after running both: Argo CD's UI and RBAC are real and they matter the moment anyone other than you looks at the cluster. Flux's minimalism is real and it matters the moment you are the one doing upgrades at 2 AM. If I were building a fleet today from a clean sheet, I would start with Flux and add Argo CD only when the dashboard became the bottleneck. If the team already lives in a dashboard, start with Argo CD. Both are graduated, both are boring in the good way, and the migration between them is mostly a repo restructure plus a secret restore, which is a much smaller cost than picking wrong and living with it.

Official sources

  • Argo CD documentation: https://argo-cd.readthedocs.io/
  • Argo CD releases: https://github.com/argoproj/argo-cd/releases
  • argocd-image-updater: https://github.com/argoproj-labs/argocd-image-updater
  • Flux: https://fluxcd.io/
  • Flux releases: https://github.com/fluxcd/flux2/releases
  • Flux v2.9 announcement: https://fluxcd.io/blog/2026/06/flux-v2.9.0/
  • Our OpenTofu + Argo CD post: https://systhoughts.com/posts/opentofu-bootstrap-kubernetes-with-argocd
  • Our Compose vs Kubernetes post: https://systhoughts.com/posts/docker-compose-vs-kubernetes-self-hosted-apps
  • Our Docker secrets (SOPS + age) post: https://systhoughts.com/posts/docker-secrets-arent-really-secrets
  • Our backup and recovery keys post: https://systhoughts.com/posts/3-2-1-backup-rule-not-enough-self-hosted-infrastructure
  • Our release-tracking workflow: https://systhoughts.com/posts/tracking-software-releases-across-forges

Are you running Argo CD, Flux, or both, and where did the control-plane versus distributed trade land for you? What did your first real drift incident teach you? Drop it in the comments.

Until next time, keep your systems thoughtful.

No comments yet