GitOps Flux CD Production Practice: 6 Deployment Patterns from Bootstrap to Multi-Cluster
Manual kubectl apply Is Destroying Your Production
3 AM, alerts are firing. You SSH into the bastion, run kubectl apply -f deployment.yaml, and the problem is temporarily solved. But the next day you discover: last night's change has no record, configuration has drifted, and nobody knows what version is actually running in the cluster.
This isn't an isolated incident — it's the daily disaster of traditional operations:
- Configuration drift: Someone directly modified a ConfigMap, and the Git declaration no longer matches cluster state
- No audit trail: kubectl operations leave no trace, making rollback impossible to trace
- Difficult emergency rollback: No idea which version to roll back to, forced to manually piece things together
- Multi-cluster nightmare: 3 clusters, 5 environments, manually syncing configs to the breaking point
- Security risk: CI systems hold cluster admin credentials — one leak compromises everything
The core principle of GitOps: Git is the Single Source of Truth. Flux CD, a CNCF graduated project, is a Kubernetes-native GitOps engine that continuously reconciles cluster state in pull mode.
Core Concepts at a Glance
| Concept | Description | Analogy |
|---|---|---|
| GitOps | Infrastructure management methodology using Git as the single source of truth | Blueprint |
| Flux CD | CNCF graduated Kubernetes GitOps engine | Autonomous construction crew |
| Kustomize | Kubernetes-native configuration customization tool, no templates needed | Layered renovation plans |
| HelmRelease | Flux custom resource for declarative Helm Chart deployment | Package manager declaration |
| Source Controller | Flux component managing Git/Helm/OCI/Bucket sources | Warehouse manager |
| Reconciliation | Continuously comparing desired state with actual state and auto-remediating | Inspection and correction |
| Progressive Delivery | Gradual delivery with canary/blue-green/AB testing | Gradually opening the door |
5 Challenges in Production Environments
Challenge 1: Multi-Environment Configuration Chaos
Dev, test, staging, production — each environment has its own YAML copy. Changing one parameter means editing 4 files; missing one is an incident.
Challenge 2: Helm Chart Version Chaos
Chart versions, values files, and dependencies are scattered everywhere. Upgrading one Chart without knowing its blast radius.
Challenge 3: Multi-Cluster Coordination Difficulties
Multiple Kubernetes clusters (public cloud, private cloud, edge nodes) with configurations that can't be unified — synchronization is entirely manual.
Challenge 4: Secrets Stored in Plaintext
Database passwords and API keys committed directly in YAML to Git — a massive security vulnerability.
Challenge 5: No Gradual Rollout Capability
All-at-once deployments mean a bad version immediately impacts all users with no way to gradually validate.
6 Production Deployment Patterns
Pattern 1: Flux Bootstrap and Initial Setup
Flux Bootstrap is the foundation of everything — it brings Flux itself under GitOps management, achieving "self-bootstrapping."
# Install Flux CLI
curl -s https://fluxcd.io/install.sh | sudo bash
# Verify cluster readiness
flux check --pre
# Bootstrap: install Flux to cluster and link Git repository
flux bootstrap github \
--owner=myorg \
--repository=fleet-infra \
--branch=main \
--path=clusters/production \
--personal=false \
--token-auth
# Verify installation
flux get kustomizations
kubectl get pods -n flux-system
After Bootstrap completes, Flux creates a clusters/production/flux-system/ directory in the Git repository containing all Flux component manifests:
# clusters/production/flux-system/gotk-components.yaml
apiVersion: v1
kind: Namespace
metadata:
name: flux-system
---
apiVersion: source.toolkit.fluxcd.io/v1
kind: GitRepository
metadata:
name: flux-system
namespace: flux-system
spec:
interval: 1m0s
ref:
branch: main
secretRef:
name: flux-system
url: ssh://git@github.com/myorg/fleet-infra.git
---
apiVersion: kustomize.toolkit.fluxcd.io/v1
kind: Kustomization
metadata:
name: flux-system
namespace: flux-system
spec:
interval: 10m0s
path: ./clusters/production
prune: true
sourceRef:
kind: GitRepository
name: flux-system
# Watch reconciliation status
flux get kustomizations --watch
# Force immediate reconciliation
flux reconcile kustomization flux-system --with-source
# Check source status
flux get sources git
Pattern 2: Kustomize Overlays for Multi-Environment Management
Using Kustomize's base/overlay pattern — one base configuration + environment-specific patches eliminates configuration duplication entirely.
fleet-infra/
├── clusters/
│ ├── production/
│ │ └── flux-system/
│ ├── staging/
│ │ └── flux-system/
│ └── development/
│ └── flux-system/
├── apps/
│ ├── base/
│ │ ├── kustomization.yaml
│ │ ├── deployment.yaml
│ │ ├── service.yaml
│ │ └── hpa.yaml
│ ├── overlays/
│ │ ├── production/
│ │ │ ├── kustomization.yaml
│ │ │ ├── deployment-patch.yaml
│ │ │ └── hpa-patch.yaml
│ │ ├── staging/
│ │ │ ├── kustomization.yaml
│ │ │ └── deployment-patch.yaml
│ │ └── development/
│ │ ├── kustomization.yaml
│ │ └── deployment-patch.yaml
# apps/base/kustomization.yaml
apiVersion: kustomize.config.k8s.io/v1beta1
kind: Kustomization
resources:
- deployment.yaml
- service.yaml
- hpa.yaml
commonLabels:
app.kubernetes.io/managed-by: flux
# apps/base/deployment.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
name: web-app
spec:
replicas: 2
selector:
matchLabels:
app: web-app
template:
metadata:
labels:
app: web-app
spec:
containers:
- name: web-app
image: myorg/web-app:latest
ports:
- containerPort: 8080
resources:
requests:
cpu: 100m
memory: 128Mi
limits:
cpu: 500m
memory: 512Mi
env:
- name: LOG_LEVEL
value: "info"
# apps/overlays/production/kustomization.yaml
apiVersion: kustomize.config.k8s.io/v1beta1
kind: Kustomization
namespace: production
resources:
- ../../base
patches:
- path: deployment-patch.yaml
- path: hpa-patch.yaml
# apps/overlays/production/deployment-patch.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
name: web-app
spec:
replicas: 5
template:
spec:
containers:
- name: web-app
env:
- name: LOG_LEVEL
value: "warn"
resources:
requests:
cpu: 250m
memory: 256Mi
limits:
cpu: "1"
memory: 1Gi
# apps/overlays/production/hpa-patch.yaml
apiVersion: autoscaling/v2
kind: HorizontalPodAutoscaler
metadata:
name: web-app
spec:
minReplicas: 5
maxReplicas: 20
metrics:
- type: Resource
resource:
name: cpu
target:
type: Utilization
averageUtilization: 70
# clusters/production/apps.yaml
apiVersion: source.toolkit.fluxcd.io/v1
kind: GitRepository
metadata:
name: web-app
namespace: flux-system
spec:
interval: 1m0s
ref:
branch: main
url: https://github.com/myorg/web-app-manifests.git
---
apiVersion: kustomize.toolkit.fluxcd.io/v1
kind: Kustomization
metadata:
name: web-app-production
namespace: flux-system
spec:
interval: 5m0s
path: ./apps/overlays/production
prune: true
sourceRef:
kind: GitRepository
name: web-app
healthChecks:
- apiVersion: apps/v1
kind: Deployment
name: web-app
namespace: production
timeout: 3m0s
# Verify Kustomize build
flux build kustomization web-app-production \
--path ./apps/overlays/production \
--kustomization-file ./clusters/production/apps.yaml
# Check reconciliation status
flux get kustomizations
Pattern 3: HelmRelease with Values from Git
Flux's HelmRelease makes Helm deployments fully declarative — values files are stored in Git, and changes automatically trigger upgrades.
# clusters/production/nginx-ingress.yaml
apiVersion: source.toolkit.fluxcd.io/v1
kind: HelmRepository
metadata:
name: ingress-nginx
namespace: flux-system
spec:
interval: 5m0s
url: https://kubernetes.github.io/ingress-nginx
---
apiVersion: source.toolkit.fluxcd.io/v1
kind: HelmRelease
metadata:
name: ingress-nginx
namespace: flux-system
spec:
interval: 10m0s
chart:
spec:
chart: ingress-nginx
version: "4.11.x"
sourceRef:
kind: HelmRepository
name: ingress-nginx
interval: 1m0s
valuesFrom:
- kind: ConfigMap
name: ingress-nginx-default-values
- kind: Secret
name: ingress-nginx-sealed-values
valuesKey: values.yaml
values:
controller:
replicaCount: 3
resources:
requests:
cpu: 200m
memory: 256Mi
limits:
cpu: "1"
memory: 512Mi
service:
type: LoadBalancer
annotations:
service.beta.kubernetes.io/aws-load-balancer-type: nlb
config:
proxy-body-size: "50m"
proxy-read-timeout: "300"
enable-real-ip: "true"
metrics:
enabled: true
serviceMonitor:
enabled: true
additionalLabels:
release: prometheus
# clusters/production/redis-ha.yaml
apiVersion: source.toolkit.fluxcd.io/v1
kind: HelmRepository
metadata:
name: bitnami
namespace: flux-system
spec:
interval: 5m0s
url: https://charts.bitnami.com/bitnami
---
apiVersion: source.toolkit.fluxcd.io/v1
kind: HelmRelease
metadata:
name: redis-ha
namespace: database
spec:
interval: 15m0s
chart:
spec:
chart: redis
version: "19.x"
sourceRef:
kind: HelmRepository
name: bitnami
install:
remediation:
retries: 3
upgrade:
remediation:
retries: 3
remediateLastFailure: true
rollback:
timeout: 5m0s
cleanupOnFail: true
values:
architecture: replication
auth:
existingSecret: redis-secret
existingSecretPasswordKey: password
master:
persistence:
enabled: true
size: 8Gi
storageClass: gp3-encrypted
resources:
requests:
cpu: 250m
memory: 512Mi
replica:
replicaCount: 2
persistence:
enabled: true
size: 8Gi
storageClass: gp3-encrypted
metrics:
enabled: true
serviceMonitor:
enabled: true
# Check Helm release status
flux get helmreleases --all-namespaces
# Force reconcile HelmRelease
flux reconcile helmrelease redis-ha -n database --with-source
# View HelmRelease details
flux describe helmrelease redis-ha -n database
# Check available Chart versions
flux get sources chart --all-namespaces
Pattern 4: Multi-Cluster Management with Flux
Flux natively supports multi-cluster — one directory per cluster, shared application configs, independent environment variables.
fleet-infra/
├── clusters/
│ ├── production/
│ │ ├── flux-system/
│ │ ├── apps.yaml
│ │ ├── infrastructure.yaml
│ │ └── monitoring.yaml
│ ├── staging/
│ │ ├── flux-system/
│ │ ├── apps.yaml
│ │ └── infrastructure.yaml
│ └── us-east-2/
│ ├── flux-system/
│ ├── apps.yaml
│ └── infrastructure.yaml
├── infrastructure/
│ ├── base/
│ └── overlays/
│ ├── production/
│ ├── staging/
│ └── us-east-2/
└── apps/
├── base/
└── overlays/
# Bootstrap production cluster
flux bootstrap github \
--owner=myorg \
--repository=fleet-infra \
--branch=main \
--path=clusters/production \
--token-auth
# Bootstrap staging cluster
flux bootstrap github \
--owner=myorg \
--repository=fleet-infra \
--branch=main \
--path=clusters/staging \
--token-auth
# Bootstrap regional cluster (using different context)
kubectl config use-context us-east-2-admin
flux bootstrap github \
--owner=myorg \
--repository=fleet-infra \
--branch=main \
--path=clusters/us-east-2 \
--token-auth
# clusters/production/infrastructure.yaml
apiVersion: kustomize.toolkit.fluxcd.io/v1
kind: Kustomization
metadata:
name: infrastructure
namespace: flux-system
spec:
interval: 10m0s
path: ./infrastructure/overlays/production
prune: true
sourceRef:
kind: GitRepository
name: flux-system
dependsOn:
- name: flux-system
---
apiVersion: kustomize.toolkit.fluxcd.io/v1
kind: Kustomization
metadata:
name: apps
namespace: flux-system
spec:
interval: 5m0s
path: ./apps/overlays/production
prune: true
sourceRef:
kind: GitRepository
name: flux-system
dependsOn:
- name: infrastructure
healthChecks:
- apiVersion: apps/v1
kind: Deployment
name: ingress-nginx-controller
namespace: ingress-nginx
# clusters/us-east-2/apps.yaml
apiVersion: kustomize.toolkit.fluxcd.io/v1
kind: Kustomization
metadata:
name: apps
namespace: flux-system
spec:
interval: 5m0s
path: ./apps/overlays/us-east-2
prune: true
sourceRef:
kind: GitRepository
name: flux-system
dependsOn:
- name: infrastructure
postBuildSubstitute:
CLUSTER_REGION: "us-east-2"
CLUSTER_NAME: "prod-us-east-2"
# Check multi-cluster reconciliation (switch contexts)
kubectl config use-context production-admin
flux get kustomizations
kubectl config use-context staging-admin
flux get kustomizations
# Suspend reconciliation for a cluster (maintenance window)
flux suspend kustomization apps
# Resume reconciliation
flux resume kustomization apps
Pattern 5: Secrets Management with SOPS/sealed-secrets
Secrets must never be committed to Git in plaintext. Flux natively integrates both SOPS and sealed-secrets solutions.
Option A: SOPS + Age
# Install age encryption tool
curl -sLO https://github.com/FiloSottile/age/releases/latest/download/age-v1.2.0-linux-amd64.tar.gz
tar xzf age-v1.2.0-linux-amd64.tar.gz
sudo mv age/age* /usr/local/bin/
# Generate key pair
age-keygen -o age.agekey
# Record the public key
age-keygen -y age.agekey
# Output similar to: age1abc123...
# Store private key in cluster Secret
kubectl create namespace flux-system || true
cat age.agekey | kubectl create secret generic sops-age \
--namespace=flux-system \
--from-file=age.agekey=/dev/stdin \
--dry-run=client -o yaml | kubectl apply -f -
# clusters/production/sops-decryption.yaml
apiVersion: source.toolkit.fluxcd.io/v1
kind: GitRepository
metadata:
name: flux-system
namespace: flux-system
spec:
interval: 1m0s
ref:
branch: main
secretRef:
name: flux-system
url: ssh://git@github.com/myorg/fleet-infra.git
ignore: |
/**//*.md
/**//*.txt
---
apiVersion: kustomize.toolkit.fluxcd.io/v1
kind: Kustomization
metadata:
name: flux-system
namespace: flux-system
spec:
interval: 10m0s
path: ./clusters/production
prune: true
sourceRef:
kind: GitRepository
name: flux-system
decryption:
provider: sops
secretRef:
name: sops-age
# Encrypt Secret file
sops --encrypt --age=age1abc123... \
--encrypted-regex '^(data|stringData)$' \
--in-place apps/overlays/production/db-secret.yaml
# Encrypted Secret file (safe to commit to Git)
apiVersion: v1
kind: Secret
metadata:
name: db-credentials
namespace: production
type: Opaque
data:
username: ENC[AES256_GCM,data:xxxxxxx,tag:yyyy==,type:str]
password: ENC[AES256_GCM,data:zzzzzzz,tag:wwww==,type:str]
sops:
kms: []
gcp_kms: []
azure_kv: []
hc_vault: []
age:
- recipient: age1abc123...
enc: |
-----BEGIN AGE ENCRYPTED FILE-----
xxxxxxxxxxxxxxxxxxxxxxx
-----END AGE ENCRYPTED FILE-----
lastmodified: "2026-06-15T10:00:00Z"
mac: ENC[AES256_GCM,data:mmmmm,tag:nnnn==,type:str]
Option B: Sealed Secrets
# Install kubeseal CLI
curl -sLO https://github.com/bitnami-labs/sealed-secrets/releases/latest/download/kubeseal-linux-amd64
sudo install -m 755 kubeseal-linux-amd64 /usr/local/bin/kubeseal
# Fetch public key from cluster
kubeseal --fetch-cert > sealed-secrets-cert.pem
# Create SealedSecret
kubectl create secret generic db-credentials \
--namespace=production \
--from-literal=username=admin \
--from-literal=password='S3cur3P@ss!' \
--dry-run=client -o yaml | \
kubeseal --cert sealed-secrets-cert.pem \
--format yaml > apps/overlays/production/db-sealedsecret.yaml
# apps/overlays/production/db-sealedsecret.yaml
apiVersion: bitnami.com/v1alpha1
kind: SealedSecret
metadata:
name: db-credentials
namespace: production
spec:
encryptedData:
username: AgBfj3k2...sealed-data...
password: AgCg7m9x...sealed-data...
template:
metadata:
name: db-credentials
namespace: production
type: Opaque
Pattern 6: Progressive Delivery with Flagger Canary
Flagger is the progressive delivery tool in the Flux ecosystem, working with Istio/NGINX/Skipper for automated canary deployments.
# Install Flagger using Helm
helm repo add flagger https://flagger.app
helm upgrade --install flagger flagger/flagger \
--namespace=flagger-system \
--create-namespace \
--set meshProvider=istio \
--set metricsServer=http://prometheus.istio-system:9090
# apps/base/canary.yaml
apiVersion: flagger.app/v1beta1
kind: Canary
metadata:
name: web-app
namespace: production
spec:
targetRef:
apiVersion: apps/v1
kind: Deployment
name: web-app
service:
port: 8080
targetPort: 8080
gateways:
- istio-system/public-gateway
hosts:
- web-app.example.com
trafficPolicy:
tls:
mode: DISABLE
analysis:
interval: 1m
threshold: 5
maxWeight: 50
stepWeight: 10
metrics:
- name: request-success-rate
thresholdRange:
min: 99
interval: 1m
- name: request-duration
thresholdRange:
max: 500
interval: 1m
webhooks:
- name: load-test
type: rollout
url: http://flagger-loadtester.test/
timeout: 5s
metadata:
cmd: "hey -z 1m -q 10 -c 2 http://web-app.production:8080/"
- name: acceptance-test
type: pre-rollout
url: http://flagger-loadtester.test/
timeout: 30s
metadata:
type: bash
cmd: "curl -sf http://web-app.canary:8080/healthz"
# Canary deployment flow visualization
# 1. New image detected → Create Canary Deployment
# 2. 0% → 10% traffic → Analyze metrics
# 3. 10% → 20% traffic → Analyze metrics
# 4. 20% → 30% traffic → Analyze metrics
# 5. 30% → 40% traffic → Analyze metrics
# 6. 40% → 50% traffic → Analyze metrics
# 7. 100% traffic → Promote to production
# Any stage fails metrics → Automatic rollback
# Check canary status
flux get kustomizations --watch
# View Flagger canary details
kubectl get canary web-app -n production -o yaml
# Manually trigger canary
flux reconcile kustomization apps --with-source
# View canary events
kubectl describe canary web-app -n production
# Force rollback
kubectl patch canary web-app -n production \
-p '{"status":{"phase":"Rollback"}}' --type=merge
5 Common Pitfalls and Correct Approaches
Pitfall 1: Ignoring Reconciliation Interval Settings
❌ Wrong:
apiVersion: kustomize.toolkit.fluxcd.io/v1
kind: Kustomization
metadata:
name: apps
namespace: flux-system
spec:
interval: 1h
sourceRef:
kind: GitRepository
name: flux-system
✅ Correct:
apiVersion: kustomize.toolkit.fluxcd.io/v1
kind: Kustomization
metadata:
name: apps
namespace: flux-system
spec:
interval: 5m0s
retryInterval: 1m0s
timeout: 3m0s
sourceRef:
kind: GitRepository
name: flux-system
Key: Set
retryIntervalto ensure quick retries on reconciliation failure, andtimeoutto prevent stuck reconciliations.
Pitfall 2: Missing Rollback Configuration in HelmRelease
❌ Wrong:
apiVersion: source.toolkit.fluxcd.io/v1
kind: HelmRelease
metadata:
name: redis
spec:
chart:
spec:
chart: redis
sourceRef:
kind: HelmRepository
name: bitnami
values:
architecture: replication
✅ Correct:
apiVersion: source.toolkit.fluxcd.io/v1
kind: HelmRelease
metadata:
name: redis
spec:
install:
remediation:
retries: 3
upgrade:
remediation:
retries: 3
remediateLastFailure: true
rollback:
timeout: 5m0s
cleanupOnFail: true
disableWait: false
uninstall:
keepHistory: true
chart:
spec:
chart: redis
sourceRef:
kind: HelmRepository
name: bitnami
values:
architecture: replication
Pitfall 3: Committing Secrets in Plaintext to Git
❌ Wrong:
apiVersion: v1
kind: Secret
metadata:
name: db-credentials
type: Opaque
stringData:
username: admin
password: S3cur3P@ss!
✅ Correct:
# Encrypt with SOPS before committing
sops --encrypt --age=age1abc123... \
--encrypted-regex '^(data|stringData)$' \
--in-place secret.yaml
# Safe to commit after encryption
apiVersion: v1
kind: Secret
metadata:
name: db-credentials
type: Opaque
data:
username: ENC[AES256_GCM,data:xxx,tag:yyy==,type:str]
password: ENC[AES256_GCM,data:zzz,tag:www==,type:str]
sops:
age:
- recipient: age1abc123...
enc: |
-----BEGIN AGE ENCRYPTED FILE-----
-----END AGE ENCRYPTED FILE-----
Pitfall 4: Missing Health Checks
❌ Wrong:
apiVersion: kustomize.toolkit.fluxcd.io/v1
kind: Kustomization
metadata:
name: apps
namespace: flux-system
spec:
interval: 5m0s
path: ./apps/overlays/production
prune: true
sourceRef:
kind: GitRepository
name: flux-system
✅ Correct:
apiVersion: kustomize.toolkit.fluxcd.io/v1
kind: Kustomization
metadata:
name: apps
namespace: flux-system
spec:
interval: 5m0s
path: ./apps/overlays/production
prune: true
sourceRef:
kind: GitRepository
name: flux-system
healthChecks:
- apiVersion: apps/v1
kind: Deployment
name: web-app
namespace: production
- apiVersion: apps/v1
kind: Deployment
name: api-server
namespace: production
timeout: 5m0s
Pitfall 5: Using HTTPS Instead of SSH for GitRepository
❌ Wrong:
flux bootstrap github \
--owner=myorg \
--repository=fleet-infra \
--branch=main \
--path=clusters/production
✅ Correct:
# Use token authentication (recommended for GitHub)
flux bootstrap github \
--owner=myorg \
--repository=fleet-infra \
--branch=main \
--path=clusters/production \
--token-auth
# Or use SSH key
ssh-keygen -t ed25519 -C "flux@production" -f flux-ssh-key
flux bootstrap github \
--owner=myorg \
--repository=fleet-infra \
--branch=main \
--path=clusters/production \
--ssh-key-algorithm=ed25519
Error Troubleshooting Quick Reference
| Error Message | Cause | Solution |
|---|---|---|
unable to clone repository |
Invalid Git credentials or network issue | Check SSH key/token in Secret, verify repository access |
artifact fetch failed |
Source Controller cannot pull artifacts | Check network policies, proxy config, Source status |
dry-run failed, error: resource exists |
Resource conflict, existing同名 resource | Use prune: true or manually clean conflicting resources |
health check failed |
Health check timeout, Pod not ready | Check Pod events and logs, verify image pull and startup |
chart pull failed |
Helm Chart pull failure | Check HelmRepository URL and authentication |
Helm install failed: timed out |
Helm install timeout | Increase timeout, check Readiness Probe configuration |
decryption failed |
SOPS decryption failure | Verify sops-age Secret exists and private key is correct |
Kustomization dependency not ready |
Dependent Kustomization not ready | Check dependsOn config, verify dependency status |
drift detected |
Cluster state doesn't match Git declaration | Check if someone manually modified cluster resources |
no matches for kind "HelmRelease" |
CRD not installed | Verify Helm Controller is installed and CRD is registered |
# General troubleshooting commands
flux check # Check Flux component status
flux get sources all # View all sources
flux get kustomizations # View Kustomization status
flux get helmreleases --all-namespaces # View HelmRelease status
flux logs --level=error # View Flux error logs
flux logs --kind=kustomization --name=apps # View specific resource logs
# Deep troubleshooting
kubectl describe gitrepository flux-system -n flux-system
kubectl describe kustomization apps -n flux-system
kubectl logs -n flux-system deploy/kustomize-controller --tail=100
kubectl logs -n flux-system deploy/source-controller --tail=100
kubectl logs -n flux-system deploy/helm-controller --tail=100
Advanced Optimization
Dependency Orchestration and Deployment Order
Flux's dependsOn implements declarative deployment ordering, ensuring infrastructure is ready before applications are deployed.
# clusters/production/dependencies.yaml
apiVersion: kustomize.toolkit.fluxcd.io/v1
kind: Kustomization
metadata:
name: crds
namespace: flux-system
spec:
interval: 10m0s
path: ./infrastructure/crds
prune: true
sourceRef:
kind: GitRepository
name: flux-system
---
apiVersion: kustomize.toolkit.fluxcd.io/v1
kind: Kustomization
metadata:
name: infrastructure
namespace: flux-system
spec:
interval: 10m0s
path: ./infrastructure/overlays/production
prune: true
sourceRef:
kind: GitRepository
name: flux-system
dependsOn:
- name: crds
healthChecks:
- apiVersion: apps/v1
kind: Deployment
name: cert-manager
namespace: cert-manager
---
apiVersion: kustomize.toolkit.fluxcd.io/v1
kind: Kustomization
metadata:
name: apps
namespace: flux-system
spec:
interval: 5m0s
path: ./apps/overlays/production
prune: true
sourceRef:
kind: GitRepository
name: flux-system
dependsOn:
- name: infrastructure
Notification and Alerting Integration
Flux Notification Controller can push reconciliation events to Slack, Teams, Discord, and more.
# clusters/production/notifications.yaml
apiVersion: notification.toolkit.fluxcd.io/v1beta3
kind: Provider
metadata:
name: slack
namespace: flux-system
spec:
type: slack
channel: flux-deployments
secretRef:
name: slack-webhook-url
---
apiVersion: notification.toolkit.fluxcd.io/v1beta3
kind: Alert
metadata:
name: slack-alert
namespace: flux-system
spec:
providerRef:
name: slack
eventSeverity: error
eventSources:
- kind: Kustomization
name: "*"
- kind: HelmRelease
name: "*"
- kind: GitRepository
name: "*"
exclusionList:
- "waiting.*"
- "reconcilation.*in_progress"
---
apiVersion: notification.toolkit.fluxcd.io/v1beta3
kind: Alert
metadata:
name: slack-info
namespace: flux-system
spec:
providerRef:
name: slack
eventSeverity: info
eventSources:
- kind: Kustomization
name: "apps"
summary: "Application deployment notification"
# Create Slack Webhook Secret
kubectl create secret generic slack-webhook-url \
--namespace=flux-system \
--from-literal=address=https://hooks.slack.com/services/T00/B00/xxx
Image Auto-Update
Flux Image Automation achieves a fully automated pipeline: "commit code → build image → auto-deploy."
# clusters/production/image-automation.yaml
apiVersion: image.toolkit.fluxcd.io/v1beta2
kind: ImageRepository
metadata:
name: web-app
namespace: flux-system
spec:
image: myorg/web-app
interval: 1m0s
secretRef:
name: registry-credentials
---
apiVersion: image.toolkit.fluxcd.io/v1beta2
kind: ImagePolicy
metadata:
name: web-app
namespace: flux-system
spec:
imageRepositoryRef:
name: web-app
policy:
semver:
range: ">=1.0.0 <2.0.0"
---
apiVersion: image.toolkit.fluxcd.io/v1beta2
kind: ImageUpdateAutomation
metadata:
name: web-app
namespace: flux-system
spec:
interval: 1m0s
sourceRef:
kind: GitRepository
name: flux-system
git:
commit:
author:
email: flux@myorg.com
name: Flux Bot
messageTemplate: |
auto: update {{ .AutomationObject }} image
{{ range .Updated.Images -}}
- {{ . }}
{{ end -}}
update:
path: ./apps/overlays/production
strategy: Setters
# apps/overlays/production/deployment-patch.yaml
# Setter markers — ImageUpdateAutomation will auto-replace
apiVersion: apps/v1
kind: Deployment
metadata:
name: web-app
annotations:
# image.fluxcd.io/setters: web-app
spec:
template:
spec:
containers:
- name: web-app
image: myorg/web-app:1.0.0 # {"$imagepolicy": "flux-system:web-app"}
# Check image policies
flux get image policies
# Check image repositories
flux get image repositories
# Check auto-updates
flux get image update-auto
# Manually trigger image scan
flux reconcile image repository web-app
GitOps Tool Comparison
| Feature | Flux CD | ArgoCD | Jenkins X | Spinnaker |
|---|---|---|---|---|
| Core Model | Pull | Pull | Push + Pull | Push |
| CNCF Status | Graduated | Graduated | Incubating | Archived |
| Multi-cluster | Native | Native | Limited | Native |
| UI Dashboard | None (optional Weaveworks) | Built-in rich UI | Built-in | Built-in rich UI |
| Helm Support | HelmRelease CRD | Helm + Helmfile | Pipeline | Helm + Bake |
| Kustomize | Native | Native | Limited | None |
| Progressive Delivery | Flagger (canary) | Argo Rollouts | None | Built-in strategies |
| Secrets Management | SOPS native integration | Vault/Sealed | Vault | Vault |
| Image Auto-Update | Image Automation | Image Updater | Pipeline | None |
| Notifications | Notification Controller | Built-in | Pipeline | Built-in |
| Learning Curve | Medium | Low | High | High |
| Resource Usage | Low (~200MB) | Medium (~500MB) | High | High |
| Best For | Declarative pure GitOps | Visual GitOps | CI/CD all-in-one | Complex release strategies |
| Community Activity | High | Very High | Low | Low |
Summary: Flux CD's design philosophy is "whatever Git says, the cluster does" — no UI interference, no room for manual operations. It uses declarative APIs and continuous reconciliation to ensure cluster state always matches the Git repository. From Bootstrap to multi-cluster, from Kustomize to HelmRelease, from SOPS to Flagger, these 6 patterns cover every scenario in production GitOps. Choosing Flux means choosing a pure, auditable, automated GitOps path.
Recommended Tools
- JSON Formatter — Format Flux YAML output for quick configuration debugging
- Base64 Encode — Encode Kubernetes Secret data
- Hash Calculator — Calculate config file hashes to verify change consistency
Try these browser-local tools — no sign-up required →