INFRA

GitOps with Argo CD and Flux: Declarative Kubernetes CD

A practical 2026 guide to GitOps on Kubernetes — pull vs push CD, Argo CD and Flux architectures, Applications and ApplicationSets, the app-of-apps pattern, sync waves and hooks, Helm and Kustomize, secrets (Sealed Secrets, External Secrets, SOPS), progressive delivery with Argo Rollouts and Flagger, and multi-cluster fleets.

By Jose Nobile | Updated 2026-07-01 | 24 min read

GitOps Principles: Pull vs Push CD

GitOps is an operating model for Kubernetes where Git is the single source of truth for both application and infrastructure state. You describe the desired state of a cluster declaratively — Deployments, Services, ConfigMaps, CRDs — and commit it to a repository. An in-cluster agent continuously reconciles the live state toward what Git says. Every change is a pull request, every rollback is a git revert, and your commit history is the audit log.

The defining distinction is pull vs push CD. In a push model (classic CI/CD like GitLab CI/CD or GitHub Actions running kubectl apply), the pipeline needs cluster credentials and pushes changes in. In the pull model, an operator running inside the cluster watches Git and applies changes itself. No external system holds kube-admin credentials, drift is corrected automatically, and the cluster can live on a private network with no inbound access.

The four GitOps principles (from the OpenGitOps project) are: the system is declarative; desired state is versioned and immutable in Git; approved changes are pulled automatically; and software agents continuously reconcile and alert on divergence. Argo CD and Flux are the two CNCF-graduated tools that implement this model — this guide covers both in depth.

# GitOps repo layout — desired state lives in Git
my-gitops-repo/
  apps/
    base/ # Kustomize/Helm base manifests
    overlays/
      staging/
      production/
  clusters/
    prod/ # Flux: per-cluster sync config
    staging/
  argocd/ # Argo CD Applications (app-of-apps)

# The core loop (pull-based):
# Git (desired) --> in-cluster agent reconciles --> live state
# drift? --> agent re-applies. CI never runs kubectl apply.

Argo CD Architecture

Argo CD is a declarative GitOps controller with a first-class Web UI, CLI, and API. Its core is the application-controller, which reconciles Application resources by comparing the desired manifests (rendered from Git) against live cluster state and reporting a sync status (Synced/OutOfSync) and health status. The repo-server clones Git and renders Helm charts, Kustomize overlays, or plain YAML into manifests. The API server backs the UI, CLI, and RBAC/SSO.

As of the 3.x line, ApplicationSet and Notifications are bundled into the core distribution — no separate installs. Argo CD 3.0 folded the applicationset-controller and the notifications controller into the default manifests, changed fine-grained RBAC so policies no longer cascade to sub-resources, and made nested ApplicationSet selectors always apply. The project ships a minor release roughly every quarter and supports the three most recent minors.

Install is a single manifest into the argocd namespace. Argo CD stores its state in Kubernetes (Applications are CRDs; cluster and repo credentials are Secrets) plus Redis for caching — there is no external database. For production, front the API server with your identity provider via OIDC/SSO, enable RBAC, and run Argo CD in its own management cluster or alongside workloads. The 3.5 line further tightens supply-chain security with internal mTLS between components and Git commit signature verification.

# Install Argo CD core components into the cluster
kubectl create namespace argocd
kubectl apply -n argocd \
  -f https://raw.githubusercontent.com/argoproj/argo-cd/v3.4.4/manifests/install.yaml

# Components created:
# argocd-application-controller reconciles Applications
# argocd-repo-server renders Helm/Kustomize
# argocd-server API + Web UI + gRPC
# argocd-applicationset-controller generators (in core since 3.0)
# argocd-notifications-controller alerts (in core since 3.0)
# redis cache

argocd login argocd.example.com --sso

Flux Architecture

Flux (Flux CD) is a set of composable GitOps Toolkit controllers rather than a monolith. source-controller fetches artifacts from GitRepository, OCIRepository, or HelmRepository sources; kustomize-controller builds and applies Kustomize overlays via server-side apply; helm-controller manages HelmRelease objects; and notification-controller handles inbound webhooks and outbound alerts. Each controller is a small, independent reconciler you adopt à la carte.

There is no bundled UI — Flux is CLI- and API-driven, which makes it lightweight and a natural fit for platform teams and fleet automation. You bootstrap Flux with flux bootstrap, which installs the controllers and commits their own manifests into your repo, so Flux itself is managed by GitOps. The Kustomization and HelmRelease CRDs then declare what to sync, from which source, how often, and whether to prune.

Flux 2.x is stable and CNCF-graduated. Flux 2.8 added Helm v4 support with server-side apply and richer health checks; Flux 2.9 (2026) introduced a CLI plugin system, server-side-apply field ignore rules for fine-grained drift control, AWS CodeCommit authentication via Workload Identity, and path-pattern directory discovery in the ArtifactGenerator for monorepos. Image automation controllers let Flux write new image tags back to Git.

# Bootstrap Flux and wire it to Git (Flux manages itself)
flux bootstrap github \
  --owner=my-org --repository=fleet-infra \
  --branch=main --path=clusters/prod --personal
---
apiVersion: source.toolkit.fluxcd.io/v1
kind: GitRepository
metadata:
  name: app-repo
  namespace: flux-system
spec:
  interval: 1m
  url: https://github.com/my-org/app-config
  ref: { branch: main }
---
apiVersion: kustomize.toolkit.fluxcd.io/v1
kind: Kustomization
metadata:
  name: apps
  namespace: flux-system
spec:
  interval: 10m
  sourceRef: { kind: GitRepository, name: app-repo }
  path: ./apps/production
  prune: true
  wait: true

Argo CD vs Flux: Choosing a CD Engine

Both tools are CNCF-graduated, pull-based, and production-proven — the choice is about ergonomics and scope. Argo CD gives you a polished multi-tenant UI, built-in SSO/RBAC, and an app-centric mental model; it excels when many teams or non-experts need visibility into what is deployed and why it is OutOfSync. Flux is a minimal, controller-per-concern toolkit that composes cleanly, has a tiny footprint, and treats everything (including itself) as reconcilable CRDs — ideal for platform teams automating large fleets.

Practical differences: Argo CD centralizes visibility (one UI across clusters) and is often run from a management cluster reaching out to workload clusters; Flux runs a copy per cluster and is driven entirely by CLI/GitOps, pairing well with Terraform and Cluster API for fleet provisioning. For progressive delivery, Argo CD pairs with Argo Rollouts, while Flux pairs with Flagger. Both integrate SOPS; both support Helm and Kustomize; both support OCI artifacts as a Git alternative.

You do not have to pick just one. A common pattern is Flux for cluster add-ons and platform components (bootstrapped and self-managed) with Argo CD for application teams that want a UI. Choose Argo CD when developer experience and multi-tenant visibility matter most; choose Flux when you want a composable, low-overhead engine and manage everything as code. Both are safe, long-term bets in 2026.

# Argo CD — rich UI, imperative-style inspection
argocd app list
argocd app get guestbook
argocd app diff guestbook
argocd app sync guestbook

# Flux — CLI over CRDs, no bundled UI
flux get kustomizations -A
flux diff kustomization apps --path=./apps/production
flux reconcile kustomization apps --with-source
flux suspend kustomization apps # pause reconciliation
flux resume kustomization apps

Applications and ApplicationSets

The Application CRD is Argo CD's atomic unit: it points at a source (repo URL, revision, path) and a destination (cluster + namespace), with a syncPolicy that can be manual or automated. Automated sync with prune: true deletes resources removed from Git, and selfHeal: true reverts manual cluster changes. This one resource is the building block for everything else.

ApplicationSet templates many Applications from a generator, eliminating copy-paste. Generators include list (static values), git (one app per directory or per file in a repo), cluster (one app per registered cluster), matrix and merge (combine generators), and pull-request generators for ephemeral preview environments. Enable goTemplate: true for full Go-template expressions in the app template.

A typical use: a git directory generator that creates one Application per folder under tenants/*, so onboarding a new tenant is just adding a directory and opening a PR. Combined with the cluster generator, an ApplicationSet can roll the same add-on out to every cluster in your fleet automatically, with per-cluster values injected from cluster labels.

apiVersion: argoproj.io/v1alpha1
kind: Application
metadata:
  name: guestbook
  namespace: argocd
spec:
  project: default
  source:
    repoURL: https://github.com/my-org/app-config
    targetRevision: main
    path: apps/guestbook/overlays/prod
  destination:
    server: https://kubernetes.default.svc
    namespace: guestbook
  syncPolicy:
    automated: { prune: true, selfHeal: true }
---
apiVersion: argoproj.io/v1alpha1
kind: ApplicationSet
metadata:
  name: tenants
  namespace: argocd
spec:
  goTemplate: true
  generators:
    - git:
        repoURL: https://github.com/my-org/app-config
        revision: main
        directories:
          - path: tenants/*
  template:
    metadata:
      name: '{{.path.basename}}'
    spec:
      project: default
      source:
        repoURL: https://github.com/my-org/app-config
        targetRevision: main
        path: '{{.path.path}}'
      destination:
        server: https://kubernetes.default.svc
        namespace: '{{.path.basename}}'
      syncPolicy:
        automated: { prune: true, selfHeal: true }

The App-of-Apps Pattern

The app-of-apps pattern uses a single root Application whose source is a directory full of other Application manifests. Syncing the root creates and manages all the child Applications, giving you one place to bootstrap an entire cluster's workloads. It is the classic way to declare 'everything this cluster runs' in one Git path and let Argo CD fan it out.

App-of-apps is ideal for bootstrapping: point one root app at argocd/apps/, commit child Application YAMLs there, and each new file becomes a managed application on the next sync. It keeps ordering simple and makes the cluster's full inventory reviewable in one PR. Many teams pair it with sync waves so platform components (CNI, ingress, cert-manager) come up before application workloads.

For dynamic sets, ApplicationSet is usually the better modern choice — it generates children programmatically instead of requiring a YAML file per app. A common hybrid is a root app-of-apps that includes one or more ApplicationSets: the root gives you a single bootstrap entry point, and the ApplicationSets handle the repetitive fan-out (per tenant, per cluster). Use app-of-apps for structure and ApplicationSet for scale.

# Root "app of apps": one Application that points at a
# directory full of child Application manifests.
apiVersion: argoproj.io/v1alpha1
kind: Application
metadata:
  name: root
  namespace: argocd
spec:
  project: default
  source:
    repoURL: https://github.com/my-org/gitops
    targetRevision: main
    path: argocd/apps # dir of Application YAMLs
  destination:
    server: https://kubernetes.default.svc
    namespace: argocd
  syncPolicy:
    automated: { prune: true, selfHeal: true }
    syncOptions: [CreateNamespace=true]

Sync Waves and Resource Hooks

Argo CD applies all resources in a sync together by default, but real deployments need ordering. Sync waves impose it: annotate resources with argocd.argoproj.io/sync-wave: "N" and Argo CD applies lower numbers first, waiting for each wave's resources to become healthy before starting the next. Use negative waves for prerequisites (namespaces, CRDs) and higher waves for dependent workloads.

Resource hooks run at phases of a sync: PreSync (e.g., a database migration Job before the app rolls), Sync, PostSync (smoke tests after), and SyncFail (cleanup on failure). Annotate a resource with argocd.argoproj.io/hook and control cleanup with hook-delete-policy (e.g., HookSucceeded to delete a Job once it passes). Hooks are how you weave imperative steps into a declarative sync.

Flux expresses the same intent differently: ordering comes from dependsOn between Kustomization or HelmRelease objects, and health-gating uses wait: true with healthChecks so a dependent won't reconcile until its prerequisite is Ready. Helm hooks continue to work under both tools for chart-level lifecycle steps. Pick sync waves/hooks in Argo CD and dependsOn in Flux to model deploy ordering.

# Ordering with sync waves (lower numbers apply first)
apiVersion: v1
kind: Namespace
metadata:
  name: app
  annotations:
    argocd.argoproj.io/sync-wave: "-1"
---
# PreSync hook: run a DB migration before the app rolls out
apiVersion: batch/v1
kind: Job
metadata:
  name: db-migrate
  annotations:
    argocd.argoproj.io/hook: PreSync
    argocd.argoproj.io/hook-delete-policy: HookSucceeded
spec:
  template:
    spec:
      restartPolicy: Never
      containers:
        - name: migrate
          image: my-org/migrator:1.4.2
          command: ["/app/migrate", "up"]

Helm and Kustomize Integration

Both tools render manifests from the same ecosystem tools rather than reinventing templating. In Argo CD, an Application's source can point at a Helm chart (with helm.values, valueFiles, and releaseName) or a Kustomize directory — the repo-server runs helm template or kustomize build and applies the result. Note Argo CD installs charts by templating them, so Helm hooks map onto Argo CD hooks.

Flux keeps Helm truly native via the helm-controller and the HelmRelease CRD, performing real Helm releases (with history and rollback) and, since Flux 2.8, Helm v4 with server-side apply. Kustomize is handled by kustomize-controller applying overlays server-side. Sources — Git, an OCI registry, or a Helm repository — are decoupled from the release, so you can pin a chart version and reconcile on an interval.

A best practice with either tool is base + overlays: a Kustomize base with per-environment overlays (staging/production) or a chart with per-environment values-*.yaml. Keep environment differences to values and patches, not forked manifests. Pin chart and image versions explicitly so a reconcile is reproducible, and let GitOps — not helm upgrade from a laptop — be the only path to production.

# Argo CD Application driving a Helm chart with values
spec:
  source:
    repoURL: https://github.com/my-org/charts
    targetRevision: main
    path: charts/api
    helm:
      releaseName: api
      valueFiles: [values-prod.yaml]
      values: |
        replicaCount: 4
        image:
          tag: 1.9.0
---
# Flux HelmRelease (Helm v4, server-side apply)
apiVersion: helm.toolkit.fluxcd.io/v2
kind: HelmRelease
metadata:
  name: api
  namespace: apps
spec:
  interval: 5m
  chart:
    spec:
      chart: api
      version: "1.9.x"
      sourceRef: { kind: HelmRepository, name: my-charts }
  values:
    replicaCount: 4

Prefer OCI artifacts over branch-based Helm repos for supply-chain integrity: push charts and manifests to an OCI registry, reference them by immutable digest, and both Argo CD and Flux can verify signatures (cosign) before applying. This turns 'what is deployed' into a cryptographically verifiable fact rather than 'whatever HEAD points at right now.'

Secrets: Sealed Secrets, ESO, SOPS

Plain Kubernetes Secrets are only base64-encoded, so committing them to Git is unsafe. GitOps has three mainstream answers. Sealed Secrets (Bitnami) encrypts a Secret with a public key whose private half never leaves the cluster; the resulting SealedSecret is safe to commit, and the in-cluster controller decrypts it into a real Secret. It needs no external system — ideal for self-contained clusters.

External Secrets Operator (ESO) keeps secrets in a real vault (AWS Secrets Manager, GCP Secret Manager, Azure Key Vault, HashiCorp Vault, and 40+ providers) and syncs them into Kubernetes via an ExternalSecret that references a SecretStore. Git holds only pointers, never ciphertext; rotation happens in the vault and ESO refreshes on an interval. ESO's v1 APIs are GA, and it is the default choice when you already run a cloud secret manager.

SOPS (with age or KMS) encrypts secret values in-place so the YAML stays diff-friendly and reviewable. Flux decrypts SOPS natively — set decryption.provider: sops on a Kustomization with an age key Secret — and Argo CD supports it via the KSOPS / argocd-vault-plugin ecosystem. Rule of thumb: Sealed Secrets for simple cluster-local needs, ESO when a vault is the source of truth, SOPS for file-level encryption that lives in Git.

# 1) Sealed Secrets — encrypt for THIS cluster, commit safely
kubectl create secret generic db --dry-run=client \
  --from-literal=password=s3cr3t -o yaml \
  | kubeseal --format yaml > sealedsecret.yaml # commit this
---
# 2) External Secrets Operator — pull from a cloud vault
apiVersion: external-secrets.io/v1
kind: ExternalSecret
metadata: { name: db }
spec:
  refreshInterval: 1h
  secretStoreRef: { name: aws-sm, kind: ClusterSecretStore }
  target: { name: db }
  data:
    - secretKey: password
      remoteRef: { key: prod/db, property: password }
---
# 3) SOPS + age — Flux decrypts on apply
# sops --encrypt --age $AGE_PUB secret.yaml > secret.enc.yaml
# On the Kustomization:
spec:
  decryption:
    provider: sops
    secretRef: { name: sops-age }

Progressive Delivery: Argo Rollouts and Flagger

Progressive delivery replaces big-bang rollouts with automated canary and blue-green releases gated by metrics. In the Argo ecosystem, Argo Rollouts introduces a Rollout CRD that replaces a Deployment and defines explicit steps — set 20% weight, pause, run an AnalysisTemplate against Prometheus, then advance or auto-roll-back. It gives you precise, ordered control and integrates with the Argo CD UI.

Flagger takes the opposite approach: you keep a standard Deployment, and a Canary resource describes the analysis (step weight, interval, metric thresholds). Flagger automatically shifts traffic up by stepWeight at each interval as long as metrics pass, and rolls back on breach — no manual step list. It was born in the service-mesh world and drives traffic via Istio, Linkerd, App Mesh, Gateway API, or supported ingress controllers.

Choosing between them: Argo Rollouts requires migrating Deployments to the Rollout CRD but gives explicit, step-by-step control and a great UI — a natural companion to Argo CD. Flagger needs zero manifest changes to your workloads and automates progression declaratively — a natural companion to Flux. Both support canary, blue-green, and metric analysis via Prometheus/Datadog/etc.; both are production-grade in 2026. Match the tool to your CD engine and how much manual control you want.

# Argo Rollouts — explicit, ordered canary steps
apiVersion: argoproj.io/v1alpha1
kind: Rollout
metadata: { name: api }
spec:
  replicas: 5
  strategy:
    canary:
      steps:
        - setWeight: 20
        - pause: { duration: 2m }
        - analysis:
            templates: [{ templateName: success-rate }]
        - setWeight: 60
        - pause: { duration: 5m }
      trafficRouting:
        istio:
          virtualService: { name: api }
---
# Flagger — automated canary driven by metrics (no step list)
apiVersion: flagger.app/v1beta1
kind: Canary
metadata: { name: api }
spec:
  targetRef: { apiVersion: apps/v1, kind: Deployment, name: api }
  analysis:
    interval: 1m
    threshold: 5
    stepWeight: 10
    maxWeight: 50
    metrics:
      - name: request-success-rate
        thresholdRange: { min: 99 }
        interval: 1m

Multi-Cluster GitOps

GitOps shines across fleets. Argo CD supports multi-cluster from a central control plane: register external clusters with argocd cluster add (it stores a per-cluster Secret with the API endpoint and credentials), then target them from an Application's destination.server. One Argo CD can manage dozens of clusters, with the UI giving a single pane of glass across all of them.

To scale this without hand-writing an Application per cluster, use an ApplicationSet with the cluster generator: it emits one Application per registered cluster, injecting cluster name, server URL, and labels into the template. This is how teams roll platform add-ons (ingress, cert-manager, monitoring) to an entire fleet from one manifest, with per-cluster overrides driven by labels on the cluster Secret.

Flux's model is one Flux per cluster, each bootstrapped to its own path in a shared 'fleet' repo — a structure that pairs naturally with Cluster API and Terraform for provisioning. There is no central UI, but the per-cluster autonomy improves blast-radius isolation. Both approaches work at scale; choose central control (Argo CD) for unified visibility, or decentralized reconcilers (Flux) for isolation and infrastructure-as-code fleet management.

# Register external clusters with the central Argo CD
argocd cluster add prod-eu --name prod-eu
argocd cluster add prod-us --name prod-us
---
# Fan one app out to every registered cluster
apiVersion: argoproj.io/v1alpha1
kind: ApplicationSet
metadata: { name: addons, namespace: argocd }
spec:
  goTemplate: true
  generators:
    - clusters: {} # every cluster secret in argocd ns
  template:
    metadata: { name: 'addons-{{.name}}' }
    spec:
      project: default
      source:
        repoURL: https://github.com/my-org/addons
        targetRevision: main
        path: base
      destination:
        server: '{{.server}}'
        namespace: kube-system
      syncPolicy:
        automated: { prune: true, selfHeal: true }

Drift Detection, Self-Heal, and Sync Policies

The reconciliation loop is what makes GitOps more than 'CI that runs kubectl.' Argo CD's syncPolicy.automated with selfHeal: true means any manual kubectl edit that drifts from Git is detected and reverted; prune: true deletes resources you removed from Git. Without automation, Argo CD still detects drift and shows OutOfSync, leaving the sync to a human — useful for change-controlled production.

Fine-tune behavior with syncOptions: ServerSideApply=true for field-manager-aware applies, CreateNamespace=true, PrunePropagationPolicy=foreground, and a retry backoff for flaky syncs. Use ignoreDifferences to stop fighting other controllers — for example, ignore /spec/replicas on a Deployment managed by an HPA, or ignore fields a mutating webhook injects, so Argo CD doesn't report perpetual drift.

Flux reconciles on an interval and enforces desired state with server-side apply; prune: true on a Kustomization garbage-collects removed resources, and Flux 2.9's field-level ignore rules let you exempt specific paths from drift correction. In both tools, the reconcile loop plus alerting (Argo CD Notifications, or Flux's notification-controller to Slack/webhooks) is what turns Git into an enforced, observable source of truth.

spec:
  syncPolicy:
    automated:
      prune: true # delete resources removed from Git
      selfHeal: true # revert manual kubectl drift
      allowEmpty: false
    syncOptions:
      - CreateNamespace=true
      - ServerSideApply=true
      - PrunePropagationPolicy=foreground
    retry:
      limit: 5
      backoff: { duration: 5s, factor: 2, maxDuration: 3m }
  # Stop fighting other controllers (e.g. an HPA owns replicas)
  ignoreDifferences:
    - group: apps
      kind: Deployment
      jsonPointers: [/spec/replicas]

CI/CD Integration and Image Automation

GitOps deliberately splits CI from CD. Your GitLab CI/CD or GitHub Actions pipeline builds and tests code, pushes an immutable, digest-pinned image to a registry, and then updates the desired state in the config repo — it never runs kubectl apply against the cluster. The agent in the cluster (Argo CD or Flux) picks up the commit and reconciles. This keeps cluster credentials out of CI entirely.

That 'update the config repo' step is automated by image automation. Flux's image-reflector-controller and image-automation-controller scan a registry (ImageRepository), select a tag by an ImagePolicy (semver, numeric, or regex), and commit the new tag back to Git via ImageUpdateAutomation — closing the loop with zero pipeline access to the cluster. Argo CD offers the equivalent via Argo CD Image Updater.

Keep two repositories: an app repo (source code, CI) and a config repo (manifests, the GitOps source of truth). CI writes only the image digest/tag into the config repo; CD reconciles it. Combine with progressive delivery so a new tag triggers a canary, and with signature verification (cosign) so only signed images are promoted. This is the canonical, secure 2026 pipeline: build → sign → bump manifest → GitOps reconcile → progressive rollout.

Real-World: Production GitOps

In production, GitOps is the deployment backbone for a fleet of Kubernetes clusters running dozens of microservices. Every change — application version bumps, config, platform add-ons — flows through a Git config repository and is reconciled by an in-cluster agent. Nothing reaches a cluster except through a reviewed, versioned commit, and rollbacks are a git revert away.

Declarative Fleet

One config repo drives every cluster. ApplicationSet cluster generators fan platform add-ons (ingress, cert-manager, monitoring) across the fleet, with per-cluster values from labels. Adding a cluster is a PR, not a runbook.

No Cluster Creds in CI

CI builds, signs, and bumps an image digest in Git; the in-cluster agent reconciles. Pipelines never hold kube-admin credentials, and clusters accept no inbound deploy traffic — a major reduction in attack surface.

Self-Healing + Audit

Automated sync with self-heal reverts drift within minutes; prune removes orphaned resources. Every deploy, rollback, and config change is an auditable commit, and progressive delivery gates releases on live metrics.

Latest Argo CD and Flux Features (2025-2026)

Argo CD 3.x (ApplicationSet + Notifications in core): Starting with Argo CD 3.0, the ApplicationSet controller and Notifications controller ship in the default install manifests — no more separate Argoproj-Labs deployments. 3.0 also tightened fine-grained RBAC (policies no longer cascade to sub-resources) and made nested ApplicationSet selectors always apply. The 3.x line follows a quarterly minor cadence with the three latest minors supported.

Argo CD 3.4 / 3.5 (supply-chain hardening): Recent 3.x releases focus on security and scale. The 3.5 line adds internal mTLS between Argo CD components and Git commit signature verification, plus native ApplicationSet management improvements. Combined with OCI artifact sources and cosign verification, Argo CD can enforce that only signed, attested manifests are ever synced to a cluster.

Flux 2.9 (June 2026): Flux 2.9 introduced a CLI plugin system (with Mirror and Schema plugins), server-side-apply field ignore rules for fine-grained drift control, AWS CodeCommit authentication via Workload Identity, and path-pattern directory discovery in the ArtifactGenerator to manage artifacts across monorepos. It builds on Flux 2.8, which brought Helm v4 support with server-side apply and enhanced health checking.

OCI as a first-class GitOps source: Both tools now treat OCI registries as a peer to Git for delivering manifests and charts. Packaging config as an immutable, digest-addressed OCI artifact — pushed by CI, verified by cosign, referenced by OCIRepository (Flux) or an OCI-typed source (Argo CD) — gives reproducible, tamper-evident deployments and decouples the delivery artifact from branch churn.

External Secrets Operator v1 GA and ESO 2.x: ESO's core APIs reached v1 stability and the 2.x line (2.6 as of mid-2026) spans 40+ providers, making external vaults the mainstream way to keep ciphertext out of Git. Paired with SOPS+age for file-level encryption and Sealed Secrets for cluster-local needs, teams now have a mature, layered secrets story that fits any GitOps topology.

Progressive delivery goes mainstream: Argo Rollouts (with Argo CD) and Flagger (with Flux) are both production-standard in 2026, with Gateway API traffic routing joining Istio/Linkerd/mesh options. The canonical modern pipeline is build → sign (cosign) → bump manifest via image automation → GitOps reconcile → metric-gated canary — an end-to-end path where every step is declarative, versioned, and verifiable.

3.0

ApplicationSet + Notifications in Core

Since Argo CD 3.0 both ship in the default manifests. RBAC no longer cascades to sub-resources; nested ApplicationSet selectors always apply.

3.5

Argo CD 3.5 mTLS + Signed Commits

Internal mTLS between components and Git commit signature verification harden the supply chain. Quarterly minors, three latest supported.

2.9

Flux 2.9 (June 2026)

CLI plugin system, server-side-apply field ignore rules, AWS CodeCommit Workload Identity, and ArtifactGenerator path patterns for monorepos.

OCI

OCI + cosign Sources

Both tools consume digest-pinned OCI artifacts and verify signatures before applying — reproducible, tamper-evident delivery beyond branch HEAD.

ESO 2.x

External Secrets Operator 2.x

v1 APIs GA, 40+ providers. Keep ciphertext out of Git with cloud vaults; layer with SOPS+age and Sealed Secrets for a complete secrets story.

GA

Progressive Delivery Standard

Argo Rollouts + Argo CD and Flagger + Flux, now with Gateway API traffic routing. Metric-gated canaries are the default safe rollout in 2026.

# Flux 2.9 image automation: bump image tags back to Git
apiVersion: image.toolkit.fluxcd.io/v1beta2
kind: ImagePolicy
metadata: { name: api, namespace: flux-system }
spec:
  imageRepositoryRef: { name: api }
  policy:
    semver: { range: ">=1.9.0" }
---
apiVersion: image.toolkit.fluxcd.io/v1beta2
kind: ImageUpdateAutomation
metadata: { name: api, namespace: flux-system }
spec:
  interval: 5m
  sourceRef: { kind: GitRepository, name: flux-system }
  git:
    commit:
      author: { name: fluxbot, email: [email protected] }
    push: { branch: main }

More Guides