ArgoCD to Flux
Why Consider Migrating?
I’ve been running ArgoCD for years, managing 90+ applications across my homelab. It was my first GitOps tool — the UI made onboarding easy and gave great visibility into deployments. So why look elsewhere?
Watching Scott Rosenberg being vehement that ArgoCD is a bad implementation while Flux is the right way to do GitOps got me thinking. This video was the final nail in the coffin:
I had to try it out — especially since I wanted to test the new Flux Operator Web UI anyway.
Let’s be honest: ArgoCD is the de facto standard GitOps tool. Most tutorials, most blog posts, most job descriptions — it’s ArgoCD. But popularity doesn’t mean best implementation. Even Flux v1 was flawed, which is why Stefan Prodan decided to rewrite it from scratch — and Flux v2 was born. It’s actively maintained, a CNCF graduated project, and has a large adopters base — including Adore Me, who also sponsor Cloud Native Bucharest.
I like rooting for the underdog.
The Problem with ArgoCD
Sync Fails and ArgoCD Gives Up
ArgoCD’s synchronization model has a fundamental flaw: when a sync fails, it stops trying. It marks the application as “Sync Failed” and waits — even if a newer commit exists that would fix the problem.
Consider this scenario:
- You push a commit that creates a CustomResource
- The CRD doesn’t exist yet — sync fails
- You push another commit that adds the CRD
- ArgoCD keeps retrying the old failed commit instead of syncing the newer fix
This violates GitOps expectations. The latest commit should be the desired state, but ArgoCD gets stuck on the past.
Flux takes a different approach: eventual consistency. It continuously reconciles toward the desired state, retrying until it succeeds. If something is temporarily broken, Flux keeps trying. When the dependency becomes available, it converges automatically.
Even Viktor Farcic — a self-proclaimed ArgoCD fan — acknowledges this problem:
“Argo CD Synchronization is BROKEN!” — Viktor argues ArgoCD’s strong consistency model causes more problems than it solves, and eventual consistency (like Flux) would be a game-changer.
Helm Charts Aren’t Really Helm
ArgoCD doesn’t actually install Helm charts — it runs helm template to render manifests, then applies them with kubectl apply. The Helm release never exists.
This means:
helm listshows nothing — Your releases are invisible to Helm tooling- Helm hooks don’t work properly — ArgoCD maps them to its own hook system, but many concepts don’t translate (no install vs upgrade differentiation)
- No native rollback — You can’t use
helm rollback; you’re stuck with ArgoCD’s mechanisms - Drift with generated values — Charts that generate random passwords or certificates show constant “OutOfSync” because each
helm templateproduces different output
Flux uses the native Helm SDK — it actually runs helm install and helm upgrade. Your releases appear in helm list, hooks work as expected, and you get full Helm functionality.
| Feature | ArgoCD | Flux |
|---|---|---|
| Method | helm template → kubectl apply | Native Helm SDK |
Visible in helm list | No | Yes |
| Full Helm hooks | Limited | Yes |
| Native rollback | No | Yes |
Git Isn’t Always the Source of Truth
The core GitOps principle: Git is the single source of truth. But with ArgoCD, that’s not always the case:
- UI modifications — You can change settings, trigger syncs, and even modify application configs through the ArgoCD UI. These changes don’t come from Git.
- Manual syncs — The “Sync” button is convenient, but it means someone can deploy without committing anything.
- Parameters and overrides — ArgoCD lets you override values at the application level, bypassing what’s in Git.
The result? Your cluster state can drift from what’s in your repository. Git says one thing, the cluster says another, and you’re not sure which is “correct.”
RBAC Bypass
ArgoCD creates its own access control layer on top of Kubernetes RBAC. Users can perform actions through the ArgoCD UI that they wouldn’t be allowed to do directly in the cluster. This is a security gap — you now have two permission systems to manage, and they can conflict.
Flux delegates entirely to Kubernetes RBAC. No separate access model, no UI actions bypassing cluster permissions.
“GitOps Is Not for Secrets”
ArgoCD’s own documentation states that GitOps isn’t suitable for secrets, pushing you toward external vault solutions. This fragments your source of truth — most resources come from Git, but secrets come from elsewhere.
Flux integrates SOPS natively. Encrypted secrets live in Git alongside everything else. One source of truth, fully auditable, no external dependencies.
My approach: I use Infisical for runtime secrets — the operator syncs secrets from Infisical to Kubernetes. But there’s a chicken-and-egg problem: on a fresh cluster, Flux needs credentials to pull from Git, and the Infisical operator needs credentials to authenticate. SOPS solves this — bootstrap secrets (git credentials, registry credentials, Infisical machine identity) are encrypted in Git. Flux decrypts them on startup, Infisical operator comes up, and from there Infisical manages everything else.
Security: Architecture Matters
ArgoCD has a monolithic architecture with a centralized API server and repo-server. This creates a larger attack surface — and the CVE history shows it.
Recent critical vulnerabilities:
- CVE-2025-55190 (CVSS 10.0) — Project API token exposes repository credentials
- CVE-2024-31989 (CVSS 9.0) — Redis exploit enables privilege escalation to cluster takeover
- CVE-2024-29893 — Repo-server DoS via malicious Helm registry
- Multiple XSS, authorization bypass, and credential exposure vulnerabilities throughout 2024-2025
The repo-server component is a recurring weak point — it’s a well-documented source of critical CVEs.
Flux’s architecture is fundamentally different: discrete, single-purpose controllers that mirror Kubernetes’ own design. Each controller has minimal permissions for its specific function. This compartmentalized approach creates a smaller attack surface and provides failure isolation — a vulnerability in one component doesn’t compromise the entire system.
Not Truly Pull-Based
ArgoCD runs in your cluster and pulls from Git — that’s the “pull-based” GitOps model. But it also exposes an API and UI that allow pushing changes directly. It’s a hybrid that breaks the purity of the model.
Why Flux?
Flux takes a stricter approach to GitOps.
Admission Controller Mutations Just Work
With Flux, you don’t have to create special rules for each mutation made by admission controllers. ArgoCD constantly shows “OutOfSync” when Kyverno mutates resources (adding labels, defaults, security contexts) — requiring explicit ignoreDifferences rules for each mutation type. Flux handles this gracefully by comparing against the actual desired state, not the pre-mutation manifest.
Reference: gitops-kyverno
| Aspect | ArgoCD | Flux |
|---|---|---|
| Source of truth | Git + UI + API | Git only |
| UI | Built-in web UI | None (CLI + optional external UIs) |
| Manual interventions | Easy via UI | Requires Git commits |
| Architecture | Centralized application server | Distributed Kubernetes controllers |
| Drift handling | Detects and can auto-heal | Reconciles by default |
Git Is Actually the Source of Truth
With Flux, there’s no UI to make ad-hoc changes. Want to deploy? Commit to Git. Want to rollback? Revert in Git. Want to change a setting? Update Git. Every change is tracked, auditable, and versioned.
Kubernetes-Native
Flux runs as a set of controllers using Kubernetes-native patterns. It feels like part of the cluster rather than an application running on top of it.
Lightweight
Flux is significantly lighter on resources than ArgoCD. Each Flux controller has a 64Mi memory request — four controllers total. ArgoCD runs multiple components including an API server, repo-server, Redis, and Dex.
The real difference shows at scale: ArgoCD keeps a full graph of every application and its resources in memory. The more objects your cluster has, the more memory ArgoCD needs. I’ve had to bump the ArgoCD application controller to 4GB for what wasn’t even a large deployment — that was unexpected.
Flux avoids this by design — each controller only tracks its own resource type, no centralized cache. Both tools can handle thousands of applications, but Flux gets there with a fraction of the resources.
Fast. Really Fast.
Flux is fast. Without dependencies, it’s lightning fast — resources reconcile almost instantly after a git push.
But what about dependency chains? If app B depends on app A, does B wait for A’s full interval before checking? No. Flux has a --requeue-dependency flag that controls how often blocked resources re-check if their dependencies are ready. Default is 30s — with 5s, you’re checking 6x more frequently.
With the Flux Operator, you can tune this via cluster.size:
| Size | Concurrent Reconciliations | Requeue Dependency |
|---|---|---|
small (default) | 5 | 10s |
medium | 10 | 5s |
large | 20 | 5s |
apiVersion: fluxcd.controlplane.io/v1
kind: FluxInstance
spec:
cluster:
size: medium # 10 concurrent, 5s requeueDependencies with dependsOn
Both Kustomizations and HelmReleases support dependsOn, letting you define deployment order:
apiVersion: kustomize.toolkit.fluxcd.io/v1
kind: Kustomization
metadata:
name: my-app
spec:
dependsOn:
- name: database
- name: secretsThis is cleaner than ArgoCD’s sync waves for managing deployment dependencies.
What About the UI?
The biggest ArgoCD advantage is the UI — visualizing application state, seeing sync status at a glance, clicking through resources. Flux traditionally has none.
But that’s changing. I’m curious to try Flux Operator Web UI — a new web interface for Flux that could bridge that gap.
ResourceSet: Templating I Want to Explore
Flux Operator includes ResourceSet — a declarative API for generating Kubernetes resources through templating. It caught my attention because it feels conceptually similar to Crossplane Compositions.
| Feature | ResourceSet | Crossplane Compositions |
|---|---|---|
| Purpose | Generate K8s resources from templates + inputs | Generate K8s resources from claims + compositions |
| Templating | Go text templates (<< inputs.name >>) | KCL, Go templates, or patch-and-transform |
| Input sources | Static, ConfigMaps, Secrets, GitHub/GitLab | Claims (XR) with schemas |
| Multi-tenant | ServiceAccount-based RBAC | Namespace isolation, RBAC |
| Drift detection | Yes | Yes (via provider reconciliation) |
This could complement Crossplane rather than replace it: Crossplane for infrastructure abstraction (databases, cloud resources), ResourceSet for application templating patterns. Something to explore.
Already Using Hub-and-Spoke?
If you’re running ArgoCD in a hub-and-spoke pattern (central cluster managing multiple targets), Flux supports this model too via flux2-hub-spoke-example.
However, Flux recommends standalone mode (Flux per cluster) for most use cases. From Stefan Prodan’s multi-cluster guide:
“Running Flux in the standalone mode offers a higher degree of security and autonomy for the clusters.”
| Mode | Pros | Cons |
|---|---|---|
| Standalone (recommended) | Reduced attack surface, no SPOF, test upgrades per-cluster | Operational overhead for bootstrapping |
| Hub-and-Spoke | Single pane of glass, less bootstrap overhead | SPOF, security risk, network complexity |
When hub-and-spoke makes sense:
- Cluster API users — The Flux hub doubles as your CAPI management cluster
- Dev/ephemeral environments — Lower security requirements, operational simplicity matters more
- Migration path — Keep your existing pattern while transitioning from ArgoCD
Flux 2.7 improvement: Workload identity support for authenticating to spoke clusters using cloud identities (AWS EKS, Azure AKS, GCP GKE) — no more static kubeconfig secrets.
Credit Where It’s Due
When I started learning Kubernetes, ArgoCD made it easy to understand GitOps visually. Seeing applications, their sync status, the resource tree — it clicked. And it’s the same for developers starting their journey with Kubernetes and GitOps today. The UI is genuinely valuable for learning and day-to-day visibility.
Another thing I miss: ArgoCD can sync a single resource. Flux reconciles at the Kustomization or HelmRelease level, so you always trigger a group of resources, not just the one you care about.
So why not have both? Flux for infrastructure (where correctness and security matter most), ArgoCD for developer-facing applications (where the UI helps teams understand what’s deployed). It’s not all-or-nothing.
Current Status
Migration complete. What started as “let me just try Flux on a few apps to get the taste of it” turned into migrating the entire cluster and decommissioning ArgoCD. Classic.
| Category | Migrated | Status |
|---|---|---|
| Apps | 46 apps — kutt, yopass, rxresume, dot-ai, freshrss, nextcloud, vaultwarden, linkwarden, n8n, wikijs, unifi, privatebin, ntfy, certmate, and more | ✅ Complete |
| Controllers | 42 controllers — cert-manager, cilium, cnpg, crossplane, flux-operator, forgejo, harbor, infisical, infisical-operator, k10, k8s-cleaner, k8tz, komoplane, kyverno, loki, mariadb-operator, otel, pocket-id, reloader, renovate, rook-ceph, s3bkp, traefik, valkey, velero, vpa, wkps, xlb, and more | ✅ Complete |
| Configs | ClusterIssuers, Crossplane providers/RBAC, webhook receiver, web UI, priority classes | ✅ Complete |
| Compositions | wdb, wapp, wsecret (Crossplane wstack) | ✅ Complete |
| Bootstrap | SOPS-encrypted secrets (git, harbor, infisical credentials) | ✅ Complete |
| ArgoCD | Decommissioned | ☠️ Gone |
The migration PRD was 1000+ lines of milestones, research, and detailed tasks. It’s done now — and I’m not looking back.
Resources
- Flux Documentation
- Flux Operator Documentation
- Flux Operator Web UI — New web interface for Flux
- flux2-kustomize-helm-example — Official example repo
- Repository Structure Guide — Monorepo best practices
- KRM-Native GitOps: Without Flux There is Nothing — Deep technical comparison
- Flux vs Argo CD Comparison — Spacelift
- GitOps Guide: ArgoCD vs Flux — CodeReliant
Further Reading
- Stairway to GitOps: How Morgan Stanley Adopted Flux — Morgan Stanley’s five-year journey from push-based CI/CD to GitOps with Flux at enterprise scale: 500 clusters, over 2,000 nodes, over 100,000 containers.
- How I Manage My Kubernetes Cluster With Git — If you want to find out what a fluxomization is, watch this
If you made it this far, scroll back up and check out the other tabs — Flux Components covers the controller architecture, Installation walks through Flux Operator setup, and My Setup shows my actual repository structure and patterns.