← Back to DevBytes

Flux CD: Complete Configuration Guide

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:

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:

# 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:

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:

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:

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:

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.

🚀 Need a reliable AI agent for your project?

Deploy Hermes Agent in 10 minutes. Managed hosting, zero DevOps.

Get Started — $23.99/mo
← Back to all articles