Kubernetes Flux GitOps: Automated Cluster Reconciliation (2026)
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.
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
HelmReleasecustom 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
ImageRepositoryobjects. - 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.
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 UI | Third-party (Weave GitOps, Capacitor) | Built-in, feature-rich |
| Multi-tenancy | Native — CRDs are namespace-scoped | AppProjects model |
| Helm support | HelmRelease CRD, full lifecycle | Application CRD with Helm source |
| Image automation | Built-in controllers | Third-party (Argo CD Image Updater) |
| OCI registry source | Native OCIRepository CRD | Supported |
| Notification system | Flexible Provider/Alert CRDs | Built-in webhooks |
| SOPS secret management | Native kustomize-controller support | Plugin-based |
| Bootstrap mechanism | flux bootstrap CLI | Helm chart / manifests |
| CNCF status | Graduated | Graduated |
| Best for | CLI-first, multi-tenant, automation | GUI-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
kubectlconfigured - Flux CLI installed (
brew install fluxcd/tap/fluxor the install script) - A GitHub personal access token (PAT) with
reposcope
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
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
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
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 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
- Generate an age key pair on your workstation.
- Store the private key as a Kubernetes Secret in the
flux-systemnamespace. - Encrypt your Kubernetes Secret manifests with
sops --encryptbefore committing to Git. - 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
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:
- Fetches the latest artifact from source-controller.
- Builds the desired manifests (renders Kustomize overlays, substitutes variables).
- Server-side applies the manifests using the Kubernetes apply machinery.
- Compares the server-side apply dry-run result against live cluster state.
- 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.
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
GitRepositoryobjects 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.