Kubernetes Flux GitOps: Automated Cluster Reconciliation (2026)

Flux GitOps Kubernetes

What Is GitOps and Why It Matters

GitOps is an operational model that uses Git as the single source of truth for declarative infrastructure and application configuration. Instead of imperative scripts that push changes into a cluster (push-based deployments), GitOps relies on an agent running inside the cluster that continuously pulls desired state from Git and reconciles the cluster to match it.

The core principles of GitOps are:

  • Declarative — the entire system is described declaratively (YAML manifests, Helm charts, Kustomize overlays).
  • Versioned and immutable — every change is a Git commit with a full audit trail, rollback via git revert, and peer review via pull requests.
  • Automatically pulled — an approved agent in the cluster continuously reconciles live state with the desired state in Git.
  • Continuously reconciled — drift (manual kubectl apply, node replacement, etc.) is detected and corrected automatically.
Pull vs Push: Traditional CI/CD pipelines push changes from outside the cluster (kubectl in a pipeline job). GitOps flips this: the cluster pulls desired state from Git, so cluster credentials never leave the cluster boundary.

In 2026, GitOps is the standard operating model for production Kubernetes. Flux v2 (also called the GitOps Toolkit) is a CNCF graduated project and one of the two dominant GitOps engines — the other being ArgoCD.

Flux v2 Architecture: The GitOps Toolkit

Flux v2 is not a monolith. It is a collection of loosely coupled Kubernetes controllers, each responsible for a single concern. This composable design lets you use only the parts you need.

Core Controllers

  • source-controller — fetches sources: Git repositories, Helm repositories, OCI registries, and S3-compatible buckets. Produces versioned artifacts (tar archives) stored in the cluster. All other controllers consume these artifacts.
  • kustomize-controller — applies Kustomize overlays (or plain YAML) from artifacts produced by source-controller. Handles health checks, dependency ordering, and drift correction.
  • helm-controller — manages the full Helm release lifecycle (install, upgrade, rollback, uninstall) from HelmRelease custom resources. Reads chart archives from source-controller.
  • notification-controller — routes events (reconciliation success/failure, image updates) to external systems: Slack, Teams, GitHub Commit Status, PagerDuty, etc.
  • image-reflector-controller — scans container registries and reflects available image tags into the cluster as ImageRepository objects.
  • image-automation-controller — reads image policies, writes updated image tags back to Git, and triggers a reconciliation cycle.

Custom Resource Definitions (CRDs)

Each controller is driven by CRDs. The main ones you will work with daily:

  • GitRepository — tells source-controller where to fetch your manifests.
  • HelmRepository / OCIRepository — Helm chart sources.
  • Kustomization — tells kustomize-controller which path in a source artifact to apply.
  • HelmRelease — declares a Helm release.
  • ImageRepository, ImagePolicy, ImageUpdateAutomation — image automation stack.
  • Provider, Alert, Receiver — notification stack.
Namespace isolation: Flux CRDs are namespace-scoped by default. Each team can own their own GitRepository and Kustomization objects inside their namespace, enabling full multi-tenancy.

Flux vs ArgoCD: Choosing the Right GitOps Engine

Both Flux and ArgoCD are CNCF graduated projects and production-proven. The right choice depends on your team's priorities.

Feature Flux v2 ArgoCD
Web UIThird-party (Weave GitOps, Capacitor)Built-in, feature-rich
Multi-tenancyNative — CRDs are namespace-scopedAppProjects model
Helm supportHelmRelease CRD, full lifecycleApplication CRD with Helm source
Image automationBuilt-in controllersThird-party (Argo CD Image Updater)
OCI registry sourceNative OCIRepository CRDSupported
Notification systemFlexible Provider/Alert CRDsBuilt-in webhooks
SOPS secret managementNative kustomize-controller supportPlugin-based
Bootstrap mechanismflux bootstrap CLIHelm chart / manifests
CNCF statusGraduatedGraduated
Best forCLI-first, multi-tenant, automationGUI-first, app-centric visibility

Teams that prefer a Kubernetes-native, CLI-centric workflow with strong multi-tenancy and image automation tend to choose Flux. Teams that need a rich visual dashboard for application health tend to prefer ArgoCD. Many large organizations run both — Flux for platform-level GitOps, ArgoCD for application team self-service.

Installing Flux: Bootstrap with GitHub

Prerequisites

  • Kubernetes cluster 1.28+ with kubectl configured
  • Flux CLI installed (brew install fluxcd/tap/flux or the install script)
  • A GitHub personal access token (PAT) with repo scope

Install the Flux CLI

# Install via official script (Linux/macOS)
curl -s https://fluxcd.io/install.sh | sudo bash

# Verify
flux --version
# flux version 2.3.0

# Check cluster prerequisites
flux check --pre

Bootstrap Flux onto Your Cluster

The flux bootstrap command installs Flux controllers, creates the GitOps repository (or uses an existing one), commits the Flux manifests into it, and sets up a deploy key so Flux can pull from the repo.

export GITHUB_TOKEN=ghp_your_token_here
export GITHUB_USER=your-github-username

flux bootstrap github \
  --owner=${GITHUB_USER} \
  --repository=fleet-infra \
  --branch=main \
  --path=clusters/my-cluster \
  --personal \
  --token-auth

After bootstrap completes, your cluster contains the Flux controllers in the flux-system namespace, and your GitHub repo has a clusters/my-cluster/flux-system/ directory with the Flux manifests committed and tracked.

# Verify all Flux controllers are running
kubectl get pods -n flux-system
# NAME                                       READY   STATUS
# helm-controller-5f7d9b8c4-x9k2p           1/1     Running
# kustomize-controller-6b9d7f8c5-m4n8q      1/1     Running
# notification-controller-7c8f9d6b4-p2r5s   1/1     Running
# source-controller-4d6c8b9f7-t3u1v         1/1     Running

Defining a GitRepository Source

A GitRepository tells source-controller where to fetch your Kubernetes manifests. Once defined, source-controller polls the repo on the specified interval and produces a versioned artifact that other controllers consume.

apiVersion: source.toolkit.fluxcd.io/v1
kind: GitRepository
metadata:
  name: my-app-source
  namespace: flux-system
spec:
  interval: 1m          # how often to poll for changes
  url: https://github.com/my-org/my-app-manifests
  ref:
    branch: main
  # For private repos, reference a Secret containing SSH key or basic auth
  secretRef:
    name: my-app-git-credentials
---
apiVersion: v1
kind: Secret
metadata:
  name: my-app-git-credentials
  namespace: flux-system
type: Opaque
stringData:
  username: git
  password: ghp_your_deploy_token

You can also pin to a specific tag or commit for immutable deployments:

spec:
  ref:
    tag: v1.4.2        # pin to a Git tag
    # OR
    commit: 9a8b7c6d   # pin to an exact commit SHA
SSH deploy keys: For production, prefer SSH deploy keys over personal access tokens. Create a key pair with flux create secret git and add the public key as a read-only deploy key in your GitHub repo settings.

Kustomization: Applying Manifests to the Cluster

A Kustomization (Flux kind, not to be confused with a Kustomize kustomization.yaml) tells kustomize-controller which path inside a source artifact to apply, how often to reconcile, and what health checks to perform.

apiVersion: kustomize.toolkit.fluxcd.io/v1
kind: Kustomization
metadata:
  name: my-app
  namespace: flux-system
spec:
  interval: 5m           # reconcile interval
  path: ./manifests/prod # path inside the GitRepository
  prune: true            # delete resources removed from Git
  sourceRef:
    kind: GitRepository
    name: my-app-source
  healthChecks:
    - apiVersion: apps/v1
      kind: Deployment
      name: my-app
      namespace: production
  timeout: 3m

Dependency Ordering with dependsOn

Use dependsOn to sequence Kustomizations — for example, install CRDs before deploying applications that use them:

apiVersion: kustomize.toolkit.fluxcd.io/v1
kind: Kustomization
metadata:
  name: my-app
  namespace: flux-system
spec:
  interval: 5m
  path: ./manifests/app
  prune: true
  sourceRef:
    kind: GitRepository
    name: my-app-source
  dependsOn:
    - name: crds          # wait for this Kustomization to be ready first
    - name: cert-manager
prune: true is powerful but irreversible in production. When you delete a manifest from Git, Flux will delete the corresponding resource from the cluster. Test with flux diff kustomization my-app before committing deletions.

Deploying Helm Charts with HelmRepository and HelmRelease

Flux provides first-class Helm support through two CRDs: HelmRepository (or OCIRepository for OCI-packaged charts) defines where charts live, and HelmRelease defines a release with values overrides.

HelmRepository Source

apiVersion: source.toolkit.fluxcd.io/v1
kind: HelmRepository
metadata:
  name: bitnami
  namespace: flux-system
spec:
  interval: 12h
  url: https://charts.bitnami.com/bitnami

HelmRelease

apiVersion: helm.toolkit.fluxcd.io/v2
kind: HelmRelease
metadata:
  name: redis
  namespace: production
spec:
  interval: 1h
  chart:
    spec:
      chart: redis
      version: ">=18.0.0 <19.0.0"   # semver constraint
      sourceRef:
        kind: HelmRepository
        name: bitnami
        namespace: flux-system
  values:
    auth:
      enabled: true
      existingSecret: redis-auth
    replica:
      replicaCount: 3
    metrics:
      enabled: true
  # Reference values from a ConfigMap or Secret (avoids storing values in Git)
  valuesFrom:
    - kind: ConfigMap
      name: redis-values-override
      optional: true

Flux's helm-controller handles the full Helm lifecycle: it installs the release on first creation, upgrades it when the chart version or values change, and can automatically roll back a failed upgrade:

  upgrade:
    remediation:
      retries: 3
  rollback:
    cleanupOnFail: true
OCI Helm charts: In 2026, most chart publishers push to OCI registries. Use OCIRepository instead of HelmRepository for charts from registries like oci://ghcr.io/org/charts.

Image Automation: Auto-Bumping Image Tags

Flux's image automation stack closes the GitOps loop for container images: when your CI pipeline pushes a new image tag to a registry, Flux detects it, picks the right tag according to a policy, writes the updated tag back to Git, and then reconciles the cluster to run the new image — all without human intervention.

Step 1: ImageRepository — Scan the Registry

apiVersion: image.toolkit.fluxcd.io/v1beta2
kind: ImageRepository
metadata:
  name: my-app
  namespace: flux-system
spec:
  image: ghcr.io/my-org/my-app
  interval: 5m
  # For private registries, provide credentials
  secretRef:
    name: ghcr-credentials

Step 2: ImagePolicy — Pick the Right Tag

apiVersion: image.toolkit.fluxcd.io/v1beta2
kind: ImagePolicy
metadata:
  name: my-app
  namespace: flux-system
spec:
  imageRepositoryRef:
    name: my-app
  policy:
    semver:
      range: ">=1.0.0 <2.0.0"   # only select 1.x.x tags

Step 3: ImageUpdateAutomation — Write the Tag Back to Git

apiVersion: image.toolkit.fluxcd.io/v1beta1
kind: ImageUpdateAutomation
metadata:
  name: flux-system
  namespace: flux-system
spec:
  interval: 5m
  sourceRef:
    kind: GitRepository
    name: my-app-source
  git:
    checkout:
      ref:
        branch: main
    commit:
      author:
        name: Flux Bot
        email: flux@techoral.com
      messageTemplate: |
        chore: update image to {{range .Updated.Images}}{{println .}}{{end}}
    push:
      branch: main
  update:
    strategy: Setters

Step 4: Annotate Your Deployment

Add a marker comment in your Deployment manifest so Flux knows which field to update:

spec:
  template:
    spec:
      containers:
      - name: my-app
        image: ghcr.io/my-org/my-app:1.2.3 # {"$imagepolicy": "flux-system:my-app"}

When Flux detects a new tag matching the policy (e.g., 1.2.4), it commits the updated image line to Git, and the Kustomization picks up the change and rolls out the new image.

Multi-Tenancy: Namespace Isolation per Team

Flux v2's namespace-scoped CRDs make multi-tenancy straightforward. Each team gets their own namespace and their own GitRepository pointing at their team's Git repository. A platform team manages the top-level Flux installation and grants teams access via RBAC — see Kubernetes RBAC Security for the patterns.

Tenant Structure

clusters/
  my-cluster/
    flux-system/          # platform team — Flux controllers
    tenants/
      team-a/
        kustomization.yaml  # points to team-a's repo
        rbac.yaml
      team-b/
        kustomization.yaml
        rbac.yaml

Tenant Kustomization (platform team creates this)

apiVersion: kustomize.toolkit.fluxcd.io/v1
kind: Kustomization
metadata:
  name: team-a
  namespace: flux-system
spec:
  interval: 5m
  path: ./clusters/staging
  prune: true
  sourceRef:
    kind: GitRepository
    name: team-a-source
  serviceAccountName: team-a-reconciler   # scoped service account
  targetNamespace: team-a                  # restrict to team's namespace

The serviceAccountName field restricts what resources Flux can create on behalf of the tenant — combine this with RBAC RoleBindings to enforce namespace boundaries. Teams cannot accidentally (or maliciously) deploy to other teams' namespaces.

Notifications: Alerts for Deployment Events

The notification-controller lets you route Flux events to Slack, Microsoft Teams, GitHub Commit Status, PagerDuty, and more via Provider and Alert CRDs.

Slack Provider

apiVersion: notification.toolkit.fluxcd.io/v1beta3
kind: Provider
metadata:
  name: slack
  namespace: flux-system
spec:
  type: slack
  channel: "#deployments"
  secretRef:
    name: slack-webhook-url   # Secret with key "address"

Alert CRD

apiVersion: notification.toolkit.fluxcd.io/v1beta3
kind: Alert
metadata:
  name: on-call-notification
  namespace: flux-system
spec:
  providerRef:
    name: slack
  eventSeverity: info          # info | error
  eventSources:
    - kind: Kustomization
      name: "*"                # watch all Kustomizations
    - kind: HelmRelease
      name: "*"
  exclusionList:
    - ".*no changes.*"         # suppress no-op reconciliations

GitHub Commit Status

apiVersion: notification.toolkit.fluxcd.io/v1beta3
kind: Provider
metadata:
  name: github-status
  namespace: flux-system
spec:
  type: github
  address: https://github.com/my-org/my-app-manifests
  secretRef:
    name: github-token

With commit status integration, each Git commit gets a green check or red X directly in GitHub showing whether Flux successfully reconciled that commit — closing the feedback loop between developers and cluster state.

Essential Flux CLI Commands

The flux CLI is your primary interface for inspecting and operating Flux resources without writing raw kubectl commands.

# Check overall Flux health
flux check

# List all GitRepository sources and their status
flux get sources git -A

# List all Kustomizations across all namespaces
flux get kustomizations -A

# List all HelmReleases
flux get helmreleases -A

# Force an immediate reconciliation (don't wait for the interval)
flux reconcile kustomization my-app --with-source

# Reconcile a specific HelmRelease
flux reconcile helmrelease redis -n production

# Suspend reconciliation (e.g., during maintenance)
flux suspend kustomization my-app

# Resume a suspended Kustomization
flux resume kustomization my-app

# Preview what Flux would apply without actually applying it
flux diff kustomization my-app

# Export a Flux object as YAML
flux export source git my-app-source

# Stream live Flux logs
flux logs --follow --level=error
flux diff: Use flux diff kustomization <name> before merging a PR to preview exactly what cluster resources would change. This is the GitOps equivalent of terraform plan.

Secret Management with SOPS and Age

Storing Kubernetes Secrets in Git as plaintext is a security anti-pattern. Flux integrates natively with Mozilla SOPS (Secrets OPerationS), allowing you to encrypt secrets with best-practice secret management and store the encrypted files safely in Git.

Workflow Overview

  1. Generate an age key pair on your workstation.
  2. Store the private key as a Kubernetes Secret in the flux-system namespace.
  3. Encrypt your Kubernetes Secret manifests with sops --encrypt before committing to Git.
  4. Flux's kustomize-controller automatically decrypts them at apply time using the private key.

Generate an Age Key and Store in Cluster

# Install age
brew install age   # or apt install age

# Generate a key pair
age-keygen -o age.agekey

# Store private key in flux-system namespace
cat age.agekey | kubectl create secret generic sops-age \
  --namespace=flux-system \
  --from-file=age.agekey=/dev/stdin

# Note the public key from the output, e.g.:
# Public key: age1ql3z7hjy54pw3hyww5ayyfg7zqgvc7w3j2elw8zmrj2kg5sfn9aqmcac8p

Create a .sops.yaml Config

# .sops.yaml (at repo root)
creation_rules:
  - path_regex: .*/secrets/.*\.yaml
    age: age1ql3z7hjy54pw3hyww5ayyfg7zqgvc7w3j2elw8zmrj2kg5sfn9aqmcac8p

Encrypt a Secret and Commit

# Encrypt in place
sops --encrypt --in-place secrets/db-credentials.yaml

# The file now contains encrypted data safe to commit:
# stringData:
#   password: ENC[AES256_GCM,data:abc123...,type:str]

Tell Flux to Decrypt

apiVersion: kustomize.toolkit.fluxcd.io/v1
kind: Kustomization
metadata:
  name: my-app
  namespace: flux-system
spec:
  interval: 5m
  path: ./manifests/prod
  sourceRef:
    kind: GitRepository
    name: my-app-source
  decryption:
    provider: sops
    secretRef:
      name: sops-age     # the age private key we stored earlier
Key rotation: When rotating age keys, re-encrypt all secrets with the new public key before removing the old private key from the cluster. Use sops updatekeys to re-encrypt in bulk.

Drift Detection and Reconciliation Loop

One of Flux's most valuable properties is automatic drift correction. In a traditional push-based workflow, if an operator runs kubectl edit deployment my-app to bump a replica count manually, that change is invisible to the CI pipeline — it persists until the next deployment overwrites it, creating silent config drift.

How Flux Detects Drift

On every reconciliation interval, kustomize-controller:

  1. Fetches the latest artifact from source-controller.
  2. Builds the desired manifests (renders Kustomize overlays, substitutes variables).
  3. Server-side applies the manifests using the Kubernetes apply machinery.
  4. Compares the server-side apply dry-run result against live cluster state.
  5. If any field differs, applies the corrective patch immediately.
# Simulate what happens when someone manually changes a Deployment
kubectl scale deployment my-app --replicas=10 -n production

# Wait up to 5 minutes, or force immediate reconciliation:
flux reconcile kustomization my-app --with-source

# Flux will revert the replica count back to whatever is in Git
# You'll see an event like:
kubectl describe kustomization my-app -n flux-system
# Events: Kustomization/my-app - server-side apply for cluster objects

When to Disable Drift Correction

Some resources should be excluded from drift correction — for example, ConfigMaps managed by an operator, or HPA-managed replica counts. Use Kustomize's patches to annotate those resources with kustomize.toolkit.fluxcd.io/ssa: ignore, or structure your Kustomization to exclude the relevant paths.

HPA and replicas: If you use a Horizontal Pod Autoscaler on a Deployment, set the spec.replicas field in your Deployment manifest to a comment or omit it entirely — otherwise Flux will constantly fight the HPA by resetting replicas to the Git value.

Audit Trail

Every reconciliation event is recorded as a Kubernetes Event on the Kustomization or HelmRelease object, and forwarded to your notification provider. Combined with Git history, you have a complete audit trail: who approved the change (PR review), when it was committed (Git timestamp), when Flux applied it (Event timestamp), and whether it succeeded or failed (Event reason).

Next Steps with Flux

With Flux v2 bootstrapped and your core resources defined, there is a rich ecosystem to explore:

  • Progressive delivery — combine Flux with Flagger for canary deployments and automated metric-based promotion.
  • Multi-cluster management — use a management cluster running Flux to reconcile spoke clusters, with per-cluster GitRepository objects pointing at cluster-specific overlay paths.
  • Monitoring — Flux exposes Prometheus metrics. See Kubernetes Monitoring with Prometheus to add Flux dashboards to your observability stack.
  • Security hardening — restrict Flux service account permissions, enable audit logging, and combine with Kubernetes security best practices.
  • Ingress automation — deploy and reconcile your Ingress controllers and rules through Flux HelmReleases.

GitOps with Flux transforms cluster management from a series of imperative commands into a fully auditable, self-healing system driven by Git. Start small — bootstrap a single cluster with one GitRepository and one Kustomization — and gradually expand to cover your entire fleet.