DevOpsil
GitOps
91%
Needs Review

GitOps in Practice: ArgoCD vs Flux for Kubernetes Deployments

Amara OkaforAmara Okafor6 min read

Both ArgoCD and Flux do GitOps on Kubernetes. Both watch a Git repository, detect drift from the desired state, and reconcile. Both are CNCF projects with strong production track records. The question teams struggle with is: which one fits their workflow and operational model?

This is a practical comparison based on real deployment patterns — not a sales pitch for either tool.

Architecture at a Glance

ArgoCD runs as a set of microservices in your cluster (api-server, repo-server, application-controller, dex, redis). It ships with a web UI that shows application health, sync status, and resource trees. Operators interact with it via the UI, CLI (argocd), or Kubernetes CRDs.

Flux is a collection of controllers installed via the Flux CLI or Helm (source-controller, kustomize-controller, helm-controller, notification-controller, image-automation-controller). No built-in web UI. Operators interact primarily through Kubernetes CRDs and kubectl.

PropertyArgoCDFlux v2
UIBuilt-in web UINone (use Grafana dashboards or Weave GitOps)
CRD typeApplication, AppProjectGitRepository, Kustomization, HelmRelease
Config languageApp of Apps, ApplicationSetKustomize overlays, Flux manifests
RBAC modelArgoCD Projects + RBACKubernetes RBAC native
Multi-tenancyProjects with namespace isolationSeparate controllers per tenant
Helm supportHelm chart deploymentHelm controller with full lifecycle
Multi-clusterHub-and-spoke (central ArgoCD)Push or pull per-cluster agents

Installation

ArgoCD

kubectl create namespace argocd
kubectl apply -n argocd -f https://raw.githubusercontent.com/argoproj/argo-cd/stable/manifests/install.yaml

# Wait for pods
kubectl -n argocd rollout status deploy/argocd-server

# Get initial admin password
kubectl -n argocd get secret argocd-initial-admin-secret \
  -o jsonpath='{.data.password}' | base64 -d

Access the UI:

kubectl port-forward svc/argocd-server -n argocd 8080:443
# https://localhost:8080 — admin / <password from above>

Flux v2

# Install Flux CLI
curl -s https://fluxcd.io/install.sh | sudo bash

# Bootstrap — creates the flux-system namespace, installs controllers,
# and commits flux manifests to your Git repo
flux bootstrap github \
  --owner=myorg \
  --repository=fleet-infra \
  --branch=main \
  --path=clusters/production \
  --personal

Flux bootstrap is opinionated — it writes its own manifests into your repo and reconciles from there. This is intentional: Flux itself is managed by GitOps.

Defining Applications

ArgoCD Application

# apps/frontend.yaml
apiVersion: argoproj.io/v1alpha1
kind: Application
metadata:
  name: frontend
  namespace: argocd
spec:
  project: default
  source:
    repoURL: https://github.com/myorg/k8s-manifests
    targetRevision: HEAD
    path: apps/frontend
  destination:
    server: https://kubernetes.default.svc
    namespace: frontend
  syncPolicy:
    automated:
      prune: true
      selfHeal: true
    syncOptions:
      - CreateNamespace=true

Apply it:

kubectl apply -f apps/frontend.yaml
# or via CLI:
argocd app create frontend --repo https://github.com/myorg/k8s-manifests \
  --path apps/frontend --dest-server https://kubernetes.default.svc \
  --dest-namespace frontend --sync-policy automated

Flux Kustomization

Flux uses two layers: a GitRepository source and a Kustomization that consumes it.

# clusters/production/sources/k8s-manifests.yaml
apiVersion: source.toolkit.fluxcd.io/v1
kind: GitRepository
metadata:
  name: k8s-manifests
  namespace: flux-system
spec:
  interval: 1m
  url: https://github.com/myorg/k8s-manifests
  ref:
    branch: main
---
# clusters/production/apps/frontend.yaml
apiVersion: kustomize.toolkit.fluxcd.io/v1
kind: Kustomization
metadata:
  name: frontend
  namespace: flux-system
spec:
  interval: 5m
  path: ./apps/frontend
  prune: true
  sourceRef:
    kind: GitRepository
    name: k8s-manifests
  targetNamespace: frontend
  healthChecks:
    - apiVersion: apps/v1
      kind: Deployment
      name: frontend
      namespace: frontend

Commit both files and Flux reconciles them automatically.

Sync Behavior and Drift Detection

Both tools poll for changes. The key difference is the default reconciliation model:

ArgoCD compares live cluster state against Git and surfaces drift in the UI. Auto-sync is opt-in per application — you can leave an app in manual sync mode and approve deployments through the UI or argocd app sync.

Flux reconciles continuously by default. If something drifts from Git, Flux corrects it on the next interval (default 5 minutes). There is no "approve before sync" concept out of the box.

This difference matters for regulated environments. If your change management process requires human approval before production changes apply, ArgoCD's manual sync mode aligns better. Flux's model requires you to build that gate into your Git workflow (e.g., protected branches with required reviews).

Helm Chart Deployment

ArgoCD with Helm

apiVersion: argoproj.io/v1alpha1
kind: Application
metadata:
  name: cert-manager
  namespace: argocd
spec:
  project: default
  source:
    repoURL: https://charts.jetstack.io
    chart: cert-manager
    targetRevision: v1.14.0
    helm:
      values: |
        installCRDs: true
        replicaCount: 2
  destination:
    server: https://kubernetes.default.svc
    namespace: cert-manager
  syncPolicy:
    automated:
      prune: true

Flux with Helm

apiVersion: source.toolkit.fluxcd.io/v1
kind: HelmRepository
metadata:
  name: jetstack
  namespace: flux-system
spec:
  interval: 12h
  url: https://charts.jetstack.io
---
apiVersion: helm.toolkit.fluxcd.io/v2
kind: HelmRelease
metadata:
  name: cert-manager
  namespace: flux-system
spec:
  interval: 1h
  chart:
    spec:
      chart: cert-manager
      version: "v1.14.0"
      sourceRef:
        kind: HelmRepository
        name: jetstack
  targetNamespace: cert-manager
  values:
    installCRDs: true
    replicaCount: 2

Flux's Helm controller handles upgrades, rollbacks, and drift correction at the Helm release level — it actually runs helm upgrade rather than applying manifests directly.

Multi-Cluster Management

ArgoCD Hub-and-Spoke

One central ArgoCD instance manages all clusters. Register remote clusters:

argocd cluster add production-us-east --name production-us-east
argocd cluster add production-eu-west --name production-eu-west

Use ApplicationSet to deploy the same app across all clusters:

apiVersion: argoproj.io/v1alpha1
kind: ApplicationSet
metadata:
  name: frontend
  namespace: argocd
spec:
  generators:
    - clusters: {}
  template:
    metadata:
      name: "{{name}}-frontend"
    spec:
      project: default
      source:
        repoURL: https://github.com/myorg/k8s-manifests
        path: "apps/frontend/overlays/{{name}}"
        targetRevision: HEAD
      destination:
        server: "{{server}}"
        namespace: frontend

Flux Per-Cluster

Flux runs inside each cluster and pulls from Git independently. No central controller. Bootstrap each cluster pointing at a cluster-specific path:

flux bootstrap github \
  --owner=myorg \
  --repository=fleet-infra \
  --path=clusters/production-us-east \
  --branch=main

The clusters/production-us-east/ path contains manifests specific to that cluster. Shared infrastructure lives in infrastructure/ and gets included via Kustomize references.

The tradeoff: Flux multi-cluster is more resilient (no single point of failure) but harder to get a unified view across clusters without an external observability layer.

RBAC and Multi-Tenancy

ArgoCD introduces its own RBAC layer through Projects:

apiVersion: argoproj.io/v1alpha1
kind: AppProject
metadata:
  name: team-payments
  namespace: argocd
spec:
  description: Payments team applications
  sourceRepos:
    - https://github.com/myorg/payments-k8s
  destinations:
    - namespace: payments-*
      server: https://kubernetes.default.svc
  clusterResourceWhitelist:
    - group: ""
      kind: Namespace

Flux relies entirely on native Kubernetes RBAC. Each team's Flux Kustomization runs under a specific service account with limited permissions — no separate abstraction layer.

Decision Framework

Choose ArgoCD when:

  • Your team values a visual UI for deployment oversight
  • You need manual approval gates before production syncs
  • You manage many clusters from a central control plane
  • Non-engineers (product managers, release managers) need visibility into deployment state

Choose Flux when:

  • You prefer pure Kubernetes-native tooling with no extra abstractions
  • You want Flux itself to be GitOps-managed (it bootstraps itself into Git)
  • Per-cluster autonomy matters more than central observability
  • You use image automation (Flux can automatically update image tags in Git when new images are pushed)

Either works well when:

  • You're deploying Helm charts at scale
  • You need solid Kustomize support
  • You want tight integration with Kubernetes RBAC

Both tools are production-grade. The "right" answer usually comes down to whether your team wants a UI-centric or CLI/GitOps-native workflow — not technical capability.

Share:

Was this article helpful?

Amara Okafor
Amara Okafor

DevSecOps Lead

Security-first mindset in everything I ship. From zero-trust architectures to supply chain security, I make sure your pipeline doesn't become your weakest link.

Related Articles

Discussion