Skip to content

Architecture

Component breakdown of the GitOps Toolkit, reconciliation model, multi-tenancy, and deployment topology.

Component Overview

FluxCD v2 is built on the GitOps Toolkit (GOTK) -- a set of composable Kubernetes operators, each with its own CRDs, controller binary, and release cycle. Unlike ArgoCD's monolithic control plane, FluxCD deploys each controller as an independent Deployment in the flux-system namespace.

Controller CRDs Managed Role
source-controller GitRepository, OCIRepository, HelmRepository, HelmChart, Bucket Acquires artifacts from external sources (Git, OCI, Helm, S3); stores them as tarballs in-cluster; acts as the source-of-truth for all downstream controllers
kustomize-controller Kustomization Fetches source artifacts, runs Kustomize builds, applies resulting manifests to the cluster via server-side apply
helm-controller HelmRelease Reconciles Helm chart installations and upgrades; pulls charts from source-controller; runs helm upgrade with drift correction
notification-controller Alert, Provider, Receiver Routes events from other controllers to external systems (Slack, Discord, Teams, GitHub commit status, webhooks)
image-reflector-controller ImageRepository, ImagePolicy Scans container image registries; evaluates semantic version policies; exposes latest image tags for automation
image-automation-controller ImageUpdateAutomation Automates Git commits to update image tags in source manifests based on ImagePolicy evaluations

System Architecture

graph TB
    subgraph Sources["External Sources"]
        Git["Git Repository"]
        OCI["OCI Registry"]
        HelmRepo["Helm Repository"]
        S3["S3 Bucket"]
    end

    subgraph Flux["Flux Control Plane (flux-system namespace)"]
        SC["source-controller"]
        KC["kustomize-controller"]
        HC["helm-controller"]
        NC["notification-controller"]
        IRC["image-reflector-controller"]
        IAC["image-automation-controller"]
    end

    subgraph K8s["Kubernetes API Server"]
        API["API Server"]
    end

    subgraph Alerts["External Alerts"]
        Slack["Slack"]
        Teams["MS Teams"]
        GHStatus["GitHub Commit Status"]
    end

    Git --> SC
    OCI --> SC
    HelmRepo --> SC
    S3 --> SC
    SC -->|"source artifacts (tarballs)"| KC
    SC -->|"source artifacts (tarballs)"| HC
    KC -->|"server-side apply"| API
    HC -->|"helm upgrade"| API
    KC -->|"events"| NC
    HC -->|"events"| NC
    SC -->|"events"| NC
    IRC -->|"image tags"| IAC
    IAC -->|"git commit"| Git
    NC --> Slack
    NC --> Teams
    NC --> GHStatus

Reconciliation Model

Every Flux controller runs an independent reconciliation loop driven by the spec.interval field on its CRDs. There is no central scheduler.

sequenceDiagram
    participant Git as Git Repository
    participant SC as source-controller
    participant KC as kustomize-controller
    participant K8s as Kubernetes API

    loop Every spec.interval (e.g. 5m)
        SC->>Git: git fetch (or OCI pull, Helm index download)
        SC->>SC: Detect revision change
        alt New revision detected
            SC->>SC: Build artifact tarball
            SC->>K8s: Update GitRepository status (Ready, artifact URL)
        end
    end

    loop Every spec.interval (e.g. 10m)
        KC->>SC: Fetch artifact tarball
        KC->>KC: Run kustomize build
        KC->>K8s: server-side apply manifests
        KC->>KC: Detect drift between desired and live
        alt Drift detected and spec.prune=true
            KC->>K8s: Delete stale resources
        end
        KC->>K8s: Update Kustomization status (Ready/Healthy)
    end

Key reconciliation behaviors: - Interval-based: Each resource defines its own spec.interval (minimum 1m, typically 5-10m for production) - Event-driven acceleration: Webhook receivers (via notification-controller) can trigger immediate reconciliation instead of waiting for the interval - Retry on failure: spec.retryInterval controls backoff when reconciliation fails (default: equal to spec.interval) - Garbage collection: When spec.prune=true, the controller deletes resources that were previously applied but are no longer present in the source

Source Controller: Artifact Management

The source-controller is the foundation of the toolkit. It acts as an artifact proxy:

  • Git repositories: Clones the repo at the specified ref (branch, tag, semver, commit SHA), applies spec.ignore path filtering, and packages the result as a tarball
  • OCI registries: Pulls OCI artifacts and exposes them as in-cluster artifacts
  • Helm repositories: Downloads the Helm chart index and individual charts
  • S3 buckets: Fetches objects from S3-compatible storage

Monorepo Performance

Using a monorepo with 10,000+ files can degrade source-controller performance. The controller must clone and package the entire repository even with path filtering. Use spec.ignore to exclude irrelevant paths, or use a separate deploy branch to keep the artifact small. For large monorepos, consider splitting into multiple GitRepository resources with narrow spec.ignore rules, or use OCI artifacts built in CI instead.

Source filtering example for monorepos:

apiVersion: source.toolkit.fluxcd.io/v1
kind: GitRepository
metadata:
  name: monorepo
  namespace: flux-system
spec:
  interval: 1m
  url: https://github.com/org/monorepo
  ref:
    branch: main
  ignore: |
    # exclude everything
    /*
    # include only the deploy directory
    !/deploy/

Deployment Topology

Single-Cluster (Default)

All controllers run in the flux-system namespace. The flux bootstrap command installs the toolkit and creates a Kustomization resource that syncs the cluster state from a Git repository.

graph LR
    Git["Git Repo"] --> SC["source-controller"]
    SC --> KC["kustomize-controller"]
    SC --> HC["helm-controller"]
    KC --> K8s["Kubernetes API"]
    HC --> K8s

Multi-Cluster (Hub and Spoke)

FluxCD supports multi-cluster deployments without a central management server. Two patterns exist:

  1. Hub and Spoke: One cluster runs Flux and reconciles resources on remote clusters using kubeconfig secrets
  2. Per-Cluster Bootstrap: Each cluster runs its own Flux instance, bootstrapped independently from the same or different Git repositories
graph TB
    subgraph Hub["Hub Cluster"]
        FluxHub["Flux (flux-system)"]
    end
    subgraph Spoke1["Spoke Cluster A"]
        K8sA["Kubernetes API"]
    end
    subgraph Spoke2["Spoke Cluster B"]
        K8sB["Kubernetes API"]
    end
    subgraph Git["Git Organization"]
        RepoA["cluster-a manifest repo"]
        RepoB["cluster-b manifest repo"]
    end

    FluxHub -->|"kubeconfig secret"| K8sA
    FluxHub -->|"kubeconfig secret"| K8sB
    RepoA --> FluxHub
    RepoB --> FluxHub

Per-Cluster Bootstrap vs Hub-Spoke

The per-cluster bootstrap pattern is recommended for security isolation: each cluster's Flux instance only has credentials for its own Git repository. The hub-spoke pattern is simpler to manage but requires the hub cluster to hold kubeconfig secrets for all spoke clusters.

Multi-Tenancy

FluxCD implements multi-tenancy through Kubernetes-native mechanisms:

  • Namespace isolation: Each tenant team deploys to their own namespace with their own GitRepository and Kustomization CRDs
  • ServiceAccount impersonation: The kustomize-controller and helm-controller impersonate the ServiceAccount specified in .spec.serviceAccountName, inheriting that account's RBAC permissions
  • Cross-namespace reference blocking: Set --no-cross-namespace-refs=true on all controllers to prevent tenants from referencing sources or secrets in other namespaces
  • Remote base blocking: Set --no-remote-bases=true on kustomize-controller to prevent Kustomize remote bases from arbitrary URLs
# Kustomize patches for multi-tenancy lockdown
patches:
  - patch: |
      - op: add
        path: /spec/template/spec/containers/0/args/-
        value: --no-cross-namespace-refs=true
    target:
      kind: Deployment
      name: "(kustomize-controller|helm-controller|notification-controller)"
  - patch: |
      - op: add
        path: /spec/template/spec/containers/0/args/-
        value: --default-service-account=default
    target:
      kind: Deployment
      name: "(kustomize-controller|helm-controller)"

Storage Model

FluxCD is stateless by design. All persistent state lives in Kubernetes resources:

Data Storage
Source artifacts PersistentVolumeClaim (source-controller artifact storage)
Reconciliation state status subresource on each CRD (GitRepository, Kustomization, HelmRelease, etc.)
Git credentials Secret resources in flux-system namespace
Tenant ServiceAccounts ServiceAccount + RoleBinding per tenant namespace
Controller logs Stdout (collected by cluster logging)

How It Works

GitOps Toolkit (GOTK) controllers, pull-based reconciliation, and image automation pipeline.

Controller Reconciliation Model

flowchart TB
    subgraph GOTK["GitOps Toolkit Controllers"]
        SC["Source Controller\n(acquires artifacts)"]
        KC["Kustomize Controller\n(applies Kustomizations)"]
        HC["Helm Controller\n(applies HelmReleases)"]
        NC["Notification Controller\n(alerts)"]
    end

    subgraph Sources["Sources"]
        GitRepo["GitRepository\n(poll interval: 1min)"]
        HelmRepo["HelmRepository"]
        OCIRepo["OCIRepository"]
    end

    subgraph Cluster["Kubernetes Cluster"]
        API["K8s API Server"]
        Resources["Deployed Resources"]
    end

    GitRepo --> SC
    HelmRepo --> SC
    OCIRepo --> SC
    SC -->|"artifact ready\nevent"| KC
    SC -->|"chart ready\nevent"| HC
    KC -->|"Server-Side Apply"| API
    HC -->|"Helm SDK"| API
    API --> Resources

Image Automation Pipeline

sequenceDiagram
    participant Registry as Container Registry
    participant IAC as Image Reflector Controller
    participant IUA as Image Update Automation
    participant Git as Git Repository
    participant SC as Source Controller
    participant KC as Kustomize Controller

    IAC->>Registry: Poll for new image tags
    IAC->>IAC: Match tag pattern (semver, regex)
    IAC->>IUA: New image: myapp:v2.1.0
    IUA->>Git: Push commit (update image tag in YAML)
    SC->>Git: Detect new commit
    SC->>KC: Trigger reconciliation
    KC->>KC: Apply updated manifests

Source Controller Artifact Management

The source controller fetches and stores artifacts (Git commits, Helm charts, OCI blobs) as gzip-compressed tarballs in its .spec.artifact storage:

  1. Acquire: Clone/fetch the Git repository or pull the Helm chart
  2. Verify: Validate checksum (SHA256) and GPG signature (if configured)
  3. Package: Create a tar.gz artifact
  4. Store: Write to the configured storage (local disk or S3-compatible bucket)
  5. Notify: Emit a Kubernetes Event and update the source resource status with the artifact URL

Other controllers watch for artifact readiness events and begin their reconciliation when a new artifact is available.

Server-Side Apply

Both the Kustomize and Helm controllers use Kubernetes Server-Side Apply (SSA) to apply resources:

  • SSA tracks field ownership via metadata.managedFields, preventing accidental overwrites by other controllers
  • Conflicts are detected when two controllers manage the same field -- Flux will not overwrite fields it does not own
  • This enables safe coexistence with other tools (e.g., cert-manager injecting annotations, external-dns modifying DNS records)

Reconciliation Triggers

Flux controllers reconcile on:

Trigger Controller Mechanism
Poll interval Source Controller spec.interval (default: 1m for GitRepository)
Git webhook Source Controller HTTP endpoint receives push events, bypasses poll wait
Artifact ready Kustomize/Helm Controller Watches source resource status changes
Manual trigger Any flux reconcile kustomization <name> --force
Dependency Kustomize Controller spec.dependsOn -- waits for upstream Kustomization to be Ready

Sources


Benchmarks

Scope

Performance characteristics, scaling limits, and resource consumption for FluxCD.

Reconciliation Performance

Scale GitRepositories HelmReleases Kustomizations Reconcile Interval
Small 10 20 10 1m
Medium 50 100 50 5m
Large 200 500 200 10m

Resource Consumption

Component CPU (idle) CPU (reconcile) Memory
source-controller 50m 500m 256Mi
kustomize-controller 50m 300m 256Mi
helm-controller 50m 500m 512Mi
image-automation 20m 200m 128Mi

Scaling Limits

Dimension Tested Recommended Bottleneck
GitRepositories per cluster 500 200 Source controller memory
HelmReleases per cluster 1,000 500 Helm controller CPU
Kustomizations per cluster 500 200 API server load

Sourcing Status

Unsourced Performance Data

The performance numbers in this document are estimated from vendor documentation, community benchmarks, and engineering judgment. They do not represent controlled benchmarks with documented test conditions. Specific hardware configurations, software versions, and test methodologies were not recorded.

Use these figures as rough guidance only. For production capacity planning, run your own benchmarks against your specific workload and infrastructure.

Sources