What is Flux CD
Flux CD is a GitOps-based continuous delivery tool for Kubernetes. It ensures that the desired state defined in your Git repository is automatically synchronized with your Kubernetes clusters. Unlike traditional CI/CD pipelines that push changes from CI servers, Flux operates on a pull-based model — it pulls configuration changes from Git and applies them to your cluster.
Flux CD is part of the Cloud Native Computing Foundation (CNCF) and has graduated to incubation status. It works by running one or more controllers inside your Kubernetes cluster that continuously monitor your Git repositories, container image registries, and the cluster state itself, reconciling any drift automatically.
Core Components of Flux
Flux CD v2 (often referred to as Flux v2) is a complete rewrite from the ground up, built on a modular architecture with these key components:
- Source Controller — Manages sources: Git repositories, Helm repositories, Buckets (S3/GCS). It fetches artifacts and makes them available to other controllers.
- Kustomize Controller — Applies Kustomize manifests from a source to the cluster.
- Helm Controller — Deploys Helm charts from Helm repositories or Git sources.
- Notification Controller — Handles event notifications to external systems (Slack, Discord, Teams, webhooks).
- Image Reflector Controller — Watches container image registries and reflects metadata about available images.
- Image Automation Controller — Automatically updates image references in Git based on policies.
Why Flux CD Matters
🚀 Deploy your AI agent in 10 minutes
Managed Hermes hosting. Zero DevOps. 100M tokens/mo included.
Try it free →Flux CD brings several critical advantages to Kubernetes deployment workflows:
GitOps at Scale
With Flux, your Git repository becomes the single source of truth. Every change — whether it's a new microservice version, a configuration update, or an infrastructure modification — must go through Git. This provides an immutable audit trail, enabling you to trace every cluster change back to a specific commit, author, and pull request.
Pull-Based Architecture Security
Traditional push-based CD systems require exposing your cluster API endpoint to external CI servers or granting them cloud provider credentials. Flux flips this model: it runs inside your cluster and pulls changes from Git. Your cluster never needs to be reachable from the internet, and you don't need to share cluster credentials with external systems.
Automatic Drift Detection and Reconciliation
Flux continuously monitors your cluster state against the desired state in Git. If someone makes an out-of-band change to a resource (via kubectl or the cloud console), Flux detects the drift and can automatically revert it within seconds. The reconciliation interval is configurable, with a default of 60 seconds for Kustomize resources.
Multi-Tenancy and Multi-Cluster Management
Flux supports multi-cluster GitOps natively. You can manage hundreds of clusters from a single Git repository structure, using Kustomize overlays to handle per-cluster or per-environment variations. This dramatically simplifies fleet management at large organizations.
Built-In Image Automation
Flux can automatically watch container image registries and update your Kubernetes manifests when new images match your defined policies (e.g., auto-deploy patch versions to staging, but require manual approval for production). This closes the loop between CI and CD without requiring additional tooling.
How to Use Flux CD
Prerequisites
Before installing Flux, you need:
- A Kubernetes cluster (version 1.25+ recommended). Local options include kind, minikube, or k3s. Cloud options include EKS, AKS, GKE.
- A Git repository you control (GitHub, GitLab, Bitbucket, or any Git provider).
- The Flux CLI installed on your local machine. You can install it via:
# macOS / Linux via Homebrew
brew install fluxcd/tap/flux
# Or via curl
curl -s https://fluxcd.io/install.sh | sudo bash
# Windows via chocolatey
choco install flux
# Verify installation
flux --version
Step 1: Bootstrap Flux into Your Cluster
Bootstrapping installs Flux controllers on your cluster and configures them to sync from your Git repository. The bootstrap command is interactive and handles everything: it creates a personal access token (or uses an existing one), sets up the Git repository structure, and deploys Flux components.
# Bootstrap for GitHub
flux bootstrap github \
--owner=your-github-username \
--repository=your-repo-name \
--branch=main \
--path=clusters/my-cluster \
--personal
# Bootstrap for GitLab
flux bootstrap gitlab \
--owner=your-gitlab-username \
--repository=your-repo-name \
--branch=main \
--path=clusters/my-cluster \
--personal
# Generic bootstrap for any Git provider (manual SSH key setup)
flux bootstrap git \
--url=ssh://git@github.com/user/repo.git \
--ssh-key-algorithm=ecdsa \
--ssh-key-ecdsa-curve=p521 \
--branch=main \
--path=clusters/my-cluster
After bootstrapping, Flux creates the following structure in your repository under the specified --path:
clusters/my-cluster/
└── flux-system/
├── gotk-components.yaml # All Flux controller deployments
├── gotk-sync.yaml # GitRepository and Kustomization for self-updates
└── kustomization.yaml # Kustomize config that ties everything together
Step 2: Understanding GitRepository Resources
The GitRepository custom resource defines a source from which Flux pulls Kubernetes manifests. It specifies the Git URL, branch, authentication, and polling interval.
apiVersion: source.toolkit.fluxcd.io/v1
kind: GitRepository
metadata:
name: podinfo
namespace: flux-system
spec:
interval: 1m
url: https://github.com/stefanprodan/podinfo
ref:
branch: master
secretRef:
name: git-credentials # Optional for private repos
---
apiVersion: kustomize.toolkit.fluxcd.io/v1
kind: Kustomization
metadata:
name: podinfo
namespace: flux-system
spec:
interval: 10m
sourceRef:
kind: GitRepository
name: podinfo
path: "./kustomize"
prune: true
wait: true
timeout: 1m
The Kustomization resource references the GitRepository source and applies the manifests found at the specified path. Key spec fields:
interval— How often Flux checks for changes and reconcilesprune— When true, Flux removes resources that were previously applied but are no longer in the source (critical for cleanup)wait— When true, Flux waits for all resources to become healthy before marking the reconciliation as successfultimeout— Maximum time Flux waits for reconciliation to completehealthChecks— Custom health check expressions to verify deployment readiness
Step 3: Defining Your Application Manifests
Your application manifests live in your Git repository at the path specified in your Kustomization resource. A typical structure uses Kustomize for environment-specific overlays:
apps/
├── base/
│ └── podinfo/
│ ├── deployment.yaml
│ ├── service.yaml
│ ├── hpa.yaml
│ └── kustomization.yaml
└── overlays/
├── staging/
│ ├── kustomization.yaml
│ └── patch-deployment-replicas.yaml
└── production/
├── kustomization.yaml
└── patch-deployment-replicas.yaml
Example base kustomization.yaml:
apiVersion: kustomize.config.k8s.io/v1beta1
kind: Kustomization
metadata:
name: podinfo-base
namespace: podinfo
resources:
- deployment.yaml
- service.yaml
- hpa.yaml
images:
- name: stefanprodan/podinfo
newTag: 6.5.2
commonLabels:
app.kubernetes.io/name: podinfo
Example overlay kustomization.yaml for staging:
apiVersion: kustomize.config.k8s.io/v1beta1
kind: Kustomization
metadata:
name: podinfo-staging
namespace: podinfo-staging
resources:
- ../../base/podinfo
patchesStrategicMerge:
- patch-deployment-replicas.yaml
images:
- name: stefanprodan/podinfo
newTag: 6.5.2
Step 4: Helm Releases with Flux
Flux can deploy Helm charts directly from Helm repositories or from Git. This is managed via the HelmRepository and HelmRelease custom resources.
First, define a Helm repository source:
apiVersion: source.toolkit.fluxcd.io/v1
kind: HelmRepository
metadata:
name: bitnami
namespace: flux-system
spec:
interval: 10m
url: https://charts.bitnami.com/bitnami
Then create a HelmRelease that references it:
apiVersion: helm.toolkit.fluxcd.io/v2
kind: HelmRelease
metadata:
name: nginx
namespace: default
spec:
interval: 5m
chart:
spec:
chart: nginx
version: 15.x
sourceRef:
kind: HelmRepository
name: bitnami
namespace: flux-system
values:
replicaCount: 2
service:
type: ClusterIP
valuesFrom:
- kind: ConfigMap
name: nginx-config
valuesKey: overrides.yaml
- kind: Secret
name: nginx-secret
valuesKey: sensitive-values.yaml
For Helm charts stored in Git, use a GitRepository source instead:
apiVersion: helm.toolkit.fluxcd.io/v2
kind: HelmRelease
metadata:
name: custom-app
namespace: default
spec:
interval: 5m
chart:
spec:
chart: ./charts/my-app
sourceRef:
kind: GitRepository
name: app-repo
namespace: flux-system
values:
environment: production
replicas: 3
HelmRelease supports advanced features like:
- Drift detection — Flux detects and corrects manual changes to Helm releases
- Rollbacks — Automatic rollback on failed upgrades with configurable retry limits
- Conditional upgrades — Only upgrade when certain conditions are met (e.g., after a specific time window)
- Post-renderers — Apply Kustomize patches after Helm renders the chart but before applying to the cluster
Step 5: Image Automation — Closing the CI/CD Loop
Flux can automatically update container image tags in your Git repository based on policies you define. This requires three resources working together:
# 1. ImageRepository — tells Flux which registry to watch
apiVersion: image.toolkit.fluxcd.io/v1beta1
kind: ImageRepository
metadata:
name: podinfo
namespace: flux-system
spec:
image: stefanprodan/podinfo
interval: 5m
---
# 2. ImagePolicy — defines which images to select (semver, alphabetical, numerical)
apiVersion: image.toolkit.fluxcd.io/v1beta1
kind: ImagePolicy
metadata:
name: podinfo-policy
namespace: flux-system
spec:
imageRepositoryRef:
name: podinfo
policy:
semver:
range: 6.x # Automatically pick up all 6.x versions
---
# 3. ImageUpdateAutomation — commits the update back to Git
apiVersion: image.toolkit.fluxcd.io/v1beta1
kind: ImageUpdateAutomation
metadata:
name: podinfo-automation
namespace: flux-system
spec:
interval: 30m
sourceRef:
kind: GitRepository
name: flux-system
git:
push:
branch: main
commit:
author:
email: fluxbot@example.com
name: fluxbot
messageTemplate: "Auto-update image {{ .Image }} to {{ .Tag }}"
update:
path: ./clusters/my-cluster
strategy: Setters # or use Labels
For this to work, your deployment YAML must include a marker comment that Flux can find and update. Use the Setters strategy:
apiVersion: apps/v1
kind: Deployment
metadata:
name: podinfo
namespace: podinfo
spec:
replicas: 2
template:
spec:
containers:
- name: podinfo
image: stefanprodan/podinfo:6.5.2 # {"$imagepolicy": "flux-system:podinfo-policy"}
Alternatively, use the Labels strategy which matches via labels rather than marker comments.
Step 6: Notifications and Alerting
Flux can send notifications to various platforms when specific events occur. Define a Provider for the platform and an Alert that references it:
apiVersion: notification.toolkit.fluxcd.io/v1
kind: Provider
metadata:
name: slack
namespace: flux-system
spec:
type: slack
channel: "#flux-alerts"
secretRef:
name: slack-webhook-url
---
apiVersion: notification.toolkit.fluxcd.io/v1
kind: Alert
metadata:
name: on-call
namespace: flux-system
spec:
providerRef:
name: slack
eventSeverity: info
eventSources:
- kind: Kustomization
name: "*"
- kind: HelmRelease
name: "*"
exclusionList:
- ".*info.*"
- ".*warning.*"
Supported providers include Slack, Discord, Microsoft Teams, Google Chat, generic webhooks, and Opsgenie/PagerDuty.
Step 7: Monitoring Flux Itself
Flux exposes Prometheus metrics on port 8080. You can scrape these with a ServiceMonitor if using Prometheus Operator:
apiVersion: monitoring.coreos.com/v1
kind: ServiceMonitor
metadata:
name: flux-system
namespace: flux-system
spec:
selector:
matchLabels:
app.kubernetes.io/part-of: flux
endpoints:
- port: http-metrics
interval: 30s
Key metrics to monitor include:
gotk_reconcile_condition— Current reconciliation status (0=fail, 1=success)gotk_reconcile_duration_seconds— How long reconciliation takesgotk_source_duration_seconds— Time to fetch sourcesgotk_resource_count— Number of managed resources
Flux Configuration Reference
GitRepository Spec Reference
apiVersion: source.toolkit.fluxcd.io/v1
kind: GitRepository
metadata:
name: example
namespace: flux-system
spec:
# Required: How often to fetch from Git (minimum 1m)
interval: 1m
# Required: Git repository URL
url: https://github.com/org/repo.git
# Optional: Git reference (branch, tag, semver, commit SHA)
ref:
branch: main
# Alternative: semver range for tags
# semver: ">=1.0.0 <2.0.0"
# Alternative: specific tag
# tag: v1.0.0
# Alternative: commit SHA
# commit: abc123def456
# Optional: Authentication secret
secretRef:
name: git-credentials
# Optional: Timeout for Git operations
timeout: 60s
# Optional: Ignore specific files/patterns
ignore:
- "*.txt"
- "docs/**"
# Optional: Include only specific paths (sparse checkout)
include:
- "apps/production/**"
# Optional: Recurse submodules
recurseSubmodules: true
# Optional: TLS verification
insecureSkipTLSVerify: false
# Optional: Git implementation (go-git or libgit2)
gitImplementation: go-git
Kustomization Spec Reference
apiVersion: kustomize.toolkit.fluxcd.io/v1
kind: Kustomization
metadata:
name: example-kustomization
namespace: flux-system
spec:
# Required: Reconciliation interval
interval: 10m
# Required: Number of retries on failure (0 means retry indefinitely)
retryInterval: 2m
# Required: Source reference
sourceRef:
kind: GitRepository # or OCIRepository
name: example
namespace: flux-system
# Required: Path within the source
path: "./apps/production"
# Optional: Prune resources no longer in source
prune: true
# Optional: Wait for all resources to become healthy
wait: true
# Optional: Timeout for reconciliation
timeout: 5m
# Optional: Target namespace for all resources (if not specified in manifests)
targetNamespace: production
# Optional: Kustomize patches
patches:
- patch: |
- op: replace
path: /spec/replicas
value: 5
target:
kind: Deployment
name: my-app
# Optional: Post-build variable substitution
postBuild:
substitute:
CLUSTER_NAME: "prod-west"
ENVIRONMENT: "production"
substituteFrom:
- kind: ConfigMap
name: cluster-config
# Optional: Decrypt secrets using SOPS
decryption:
provider: sops
secretRef:
name: sops-age-key
# Optional: Health check configurations
healthChecks:
- kind: Deployment
name: my-app
namespace: production
timeout: 30s
# Optional: Dependency ordering
dependsOn:
- name: infrastructure
namespace: flux-system
# Optional: Force apply (recreate resources that can't be updated)
force: false
# Optional: Suspend reconciliation
suspend: false
HelmRelease Spec Reference
apiVersion: helm.toolkit.fluxcd.io/v2
kind: HelmRelease
metadata:
name: example-helmrelease
namespace: default
spec:
interval: 5m
suspend: false
# Chart specification
chart:
spec:
chart: nginx
version: 15.1.0
sourceRef:
kind: HelmRepository
name: bitnami
namespace: flux-system
interval: 1m
reconcileStrategy: ChartVersion # or Revision, or Digest
# Values (inline)
values:
replicaCount: 3
service:
type: LoadBalancer
# Values from external sources
valuesFrom:
- kind: ConfigMap
name: nginx-values
valuesKey: values.yaml
- kind: Secret
name: nginx-secret-values
valuesKey: sensitive.yaml
# Upgrade configuration
upgrade:
timeout: 10m
remediationStrategy: rollbackOnFailure
force: false
disableWait: false
# Rollback configuration
rollback:
timeout: 10m
disableWait: false
# Install configuration
install:
createNamespace: true
remediation:
retries: 3
disableWait: false
# Uninstall configuration
uninstall:
keepHistory: false
disableWait: false
# Test configuration
test:
enable: true
timeout: 5m
# Drift detection
driftDetection:
mode: enabled
ignore:
- paths: ["/spec/replicas"]
target:
kind: Deployment
# Post-renderers (apply Kustomize after Helm renders)
postRenderers:
- kustomize:
patches:
- patch: |
- op: add
path: /metadata/annotations/env
value: production
target:
kind: Deployment
# Dependency ordering
dependsOn:
- name: cert-manager
namespace: cert-manager
Best Practices for Flux CD Configuration
1. Repository Structure Design
Organize your GitOps repository thoughtfully. A proven pattern separates infrastructure from applications and uses overlays for environments:
clusters/
├── production/
│ ├── flux-system/ # Flux bootstrapped config
│ ├── infrastructure/ # Cluster-wide infra (ingress, cert-manager, monitoring)
│ │ ├── ingress-nginx.yaml
│ │ ├── cert-manager.yaml
│ │ └── kustomization.yaml
│ └── apps/ # Application deployments
│ ├── app-a/
│ │ ├── gitrepository.yaml
│ │ ├── kustomization.yaml
│ │ └── helmrelease.yaml
│ └── app-b/
├── staging/
│ └── ... # Same structure, different overlays
└── base/
└── ... # Shared base configurations
2. Use Kustomize for Environment Variations
Never duplicate entire manifests between environments. Use Kustomize overlays to patch only what differs — replica counts, resource limits, ingress hosts, image tags. This dramatically reduces configuration drift between environments.
# base/deployment.yaml — common across all environments
apiVersion: apps/v1
kind: Deployment
metadata:
name: my-app
spec:
template:
spec:
containers:
- name: app
image: my-app
resources:
requests:
cpu: 100m
memory: 128Mi
# overlays/production/patch-resources.yaml — production-specific overrides
apiVersion: apps/v1
kind: Deployment
metadata:
name: my-app
spec:
template:
spec:
containers:
- name: app
resources:
requests:
cpu: 500m
memory: 512Mi
limits:
cpu: 2
memory: 2Gi
3. Implement Progressive Delivery with Dependencies
Use the dependsOn field to enforce ordering. Infrastructure (CRDs, operators, ingress controllers) must be healthy before applications that depend on them. Chain Kustomizations to create deployment waves:
# Wave 1: Infrastructure
apiVersion: kustomize.toolkit.fluxcd.io/v1
kind: Kustomization
metadata:
name: infrastructure
namespace: flux-system
spec:
sourceRef:
kind: GitRepository
name: flux-system
path: ./infrastructure
prune: true
wait: true
---
# Wave 2: Platform services
apiVersion: kustomize.toolkit.fluxcd.io/v1
kind: Kustomization
metadata:
name: platform-services
namespace: flux-system
spec:
dependsOn:
- name: infrastructure
sourceRef:
kind: GitRepository
name: flux-system
path: ./platform-services
prune: true
wait: true
---
# Wave 3: Applications
apiVersion: kustomize.toolkit.fluxcd.io/v1
kind: Kustomization
metadata:
name: applications
namespace: flux-system
spec:
dependsOn:
- name: platform-services
sourceRef:
kind: GitRepository
name: flux-system
path: ./apps
prune: true
wait: true
4. Always Enable Prune and Wait
Set prune: true on all Kustomizations to ensure Flux removes resources deleted from Git. Set wait: true to block progression until resources are healthy. Without prune, your cluster accumulates orphaned resources. Without wait, dependent resources might fail because prerequisites aren't ready yet.
5. Use SOPS for Secret Management
Never commit plain-text secrets to Git. Flux integrates with Mozilla SOPS to encrypt secrets at rest in your repository. Flux decrypts them at reconciliation time using keys stored in your cluster.
# Create an Age encryption key
age-keygen -o age.key
# Store the private key as a secret in the cluster
cat age.key | kubectl create secret generic sops-age-key \
--namespace=flux-system \
--from-file=age.agekey=/dev/stdin
# Reference the secret in your Kustomization
apiVersion: kustomize.toolkit.fluxcd.io/v1
kind: Kustomization
metadata:
name: secrets
namespace: flux-system
spec:
decryption:
provider: sops
secretRef:
name: sops-age-key
sourceRef:
kind: GitRepository
name: flux-system
path: ./secrets
Encrypt your secrets file before committing:
sops --encrypt --age=age1xxxxx... secrets.yaml > secrets.enc.yaml
6. Set Appropriate Reconciliation Intervals
Balance between responsiveness and resource usage. Default intervals work well for most cases:
- GitRepository sources: 1-5 minutes for application repos, 10-30 minutes for infrastructure repos that change infrequently
- HelmRepository sources: 10-60 minutes — Helm chart index changes are rare
- Kustomization reconciliation: 5-30 minutes — longer intervals reduce API server load on large clusters
- ImageRepository scanning: 1-5 minutes for staging, 5-30 minutes for production
7. Implement Cross-Cluster Sharing with OCIRepository
For managing multiple clusters, consider packaging your Kustomize overlays as OCI artifacts and referencing them via OCIRepository instead of GitRepository. This decouples your cluster configurations from a single Git repository and allows versioned artifact distribution.
apiVersion: source.toolkit.fluxcd.io/v1beta2
kind: OCIRepository
metadata:
name: cluster-manifests
namespace: flux-system
spec:
interval: 5m
url: oci://ghcr.io/my-org/cluster-manifests
ref:
semver: ">=1.0.0"
8. Use Notification Alerts Effectively
Configure alerts for reconciliation failures and important events, but filter out noise. Set eventSeverity to error for production alerts to avoid alert fatigue from informational events. Use exclusionList to filter routine success messages.
9. Version Your Flux System Components
Pin Flux controller versions in your bootstrapped gotk-components.yaml. When upgrading, use the Flux CLI to generate updated manifests rather than manually editing:
flux install --version=2.3.0 --export > clusters/production/flux-system/gotk-components.yaml
Commit this change and let Flux self-update. This gives you a Git-versioned, reviewable upgrade path.
10. Test Changes with Flamingo (Flux Argo Subsystem)
Before merging changes to your main branch, validate them against a live cluster using Flamingo or a dedicated testing cluster. Run flux diff kustomization to preview changes:
# See what changes would be applied
flux diff kustomization my-app --kustomization-file=./clusters/production/apps/my-app.yaml
Troubleshooting Common Issues
Reconciliation Failing
Check the status of your Kustomization or HelmRelease:
# Get detailed status
flux get kustomization my-app --status --watch
# View events
kubectl describe kustomization my-app -n flux-system
# Check Flux logs
kubectl logs -n flux-system deployment/kustomize-controller -f
Source Fetch Failures
Verify your GitRepository can fetch successfully:
flux get source git my-repo
kubectl describe gitrepository my-repo -n flux-system
Common causes: expired credentials, branch renamed, SSH key misconfigured, or Git server unreachable.
Helm Release Stuck
Helm releases can get stuck in pending-upgrade or pending-install state. Force a reset:
flux suspend helmrelease my-release
# Wait for suspension to take effect
flux resume helmrelease my-release --wait
Or force a fresh reconciliation:
flux reconcile helmrelease my-release --force
Conclusion
Flux CD provides a complete, production-tested GitOps solution for Kubernetes. Its modular architecture lets you adopt exactly the components you need — from basic Git-to-cluster synchronization with Kustomize, to advanced Helm release management, to fully automated image updates that close the CI/CD loop. By following the configuration patterns and best practices outlined in this guide, you can build a robust, secure, and scalable continuous delivery pipeline where your Git repository serves as the single source of truth for your entire infrastructure and application estate. The pull-based model, combined with continuous drift detection, gives you confidence that what's in Git is exactly what's running in your cluster — at all times.