Skip to content

Flux HelmReleases and the helm-controller in production

Use Flux helm-controller to manage Helm releases declaratively — HelmRepository sources, HelmRelease values, drift detection, and upgrade strategies.

You have Flux installed and now have to deploy Helm charts the GitOps way — no helm install on a workstation, no values drift between environments, no surprise rollback because someone re-ran an old script. The HelmRelease CRD plus the helm-controller turn Helm into a fully declarative resource. This article is the operating procedure: define the chart source, version-pin it, write values in Git, and let the controller reconcile.

How to verify

The helm-controller should be running:

kubectl -n flux-system get deploy helm-controller

Helm sources should be registered:

flux get sources helm

HelmReleases should show their last applied revision:

flux get helmreleases -A

The underlying Helm release should match what Helm itself sees:

helm list -A

What’s happening

Flux splits Helm into two CRDs. HelmRepository is a source — it points at a Helm chart repo (HTTP, OCI registry, or git tree containing charts), and the source-controller polls it on an interval, pulling indexes and chart tarballs into the cluster. HelmRelease is a release — it references a HelmRepository (or a GitRepository if the chart lives in your gitops repo), pins a chart version, sets values, and tells the helm-controller to install/upgrade.

The helm-controller is the thing that actually talks to the Kubernetes API as if it were running helm install. It computes the release name (defaults to the HelmRelease name), runs the chart’s templates against the values, writes Secrets containing release state into the cluster, and applies the rendered manifests. Drift detection is built in — if you kubectl edit a Deployment that belongs to a HelmRelease, the next reconcile interval will revert your change.

The procedure

  1. Add a HelmRepository. Point at the chart source — Bitnami in this example:

    apiVersion: source.toolkit.fluxcd.io/v1
    kind: HelmRepository
    metadata:
      name: bitnami
      namespace: flux-system
    spec:
      interval: 12h
      url: https://charts.bitnami.com/bitnami

    Commit this to your infrastructure/controllers/ directory. The source-controller will index the repo and the index becomes available cluster-wide.

  2. Write a HelmRelease. This is the actual release:

    apiVersion: helm.toolkit.fluxcd.io/v2
    kind: HelmRelease
    metadata:
      name: redis
      namespace: cache
    spec:
      interval: 15m
      timeout: 10m
      chart:
        spec:
          chart: redis
          version: '20.11.4'
          sourceRef:
            kind: HelmRepository
            name: bitnami
            namespace: flux-system
          interval: 1h
      install:
        remediation:
          retries: 3
      upgrade:
        remediation:
          retries: 3
          remediateLastFailure: true
        cleanupOnFail: true
      values:
        architecture: replication
        auth:
          existingSecret: redis-auth
        master:
          persistence:
            size: 8Gi
        replica:
          replicaCount: 2

    The version field is a literal — 20.11.4, not ^20 or ~20.11. Pin it. If you want auto-update, use flux-image-automation on a separate watched range, not Helm semver matching.

  3. Layer values via valuesFrom. Secrets and large config blocks belong in their own resources:

    spec:
      values:
        master:
          persistence:
            size: 8Gi
      valuesFrom:
        - kind: ConfigMap
          name: redis-extra-values
        - kind: Secret
          name: redis-auth-values
          optional: false

    Multiple sources merge in order; later ones override earlier ones. Use this to keep secrets out of the main HelmRelease spec.

  4. Pull from OCI registries. Modern charts publish to OCI:

    apiVersion: source.toolkit.fluxcd.io/v1beta2
    kind: HelmRepository
    metadata:
      name: ingress-nginx
      namespace: flux-system
    spec:
      type: oci
      interval: 12h
      url: oci://registry-1.docker.io/bitnamicharts

    The OCI flow has the same UX as the HTTP flow — the source-controller pulls the chart tarball via OCI manifests.

  5. Test the install/upgrade path. Apply your HelmRelease via Git push:

    git add infrastructure/controllers/redis.yaml
    git commit -m "feat: add redis HelmRelease"
    git push
    flux reconcile kustomization infrastructure --with-source
    flux get helmreleases -n cache

    The HelmRelease should report Release reconciliation succeeded. If it fails, flux events --for HelmRelease/redis shows the chart render error.

  6. Bump a chart version. Edit the version field, push, observe. The helm-controller’s upgrade.remediation block decides what happens on failure — retries: 3 plus remediateLastFailure: true means three upgrade attempts, then rollback to the last successful release.

Operational notes

  • spec.interval is reconciliation cadence, not Helm poll cadence. The chart source is polled per the HelmRepository’s interval. A HelmRelease with a 15m interval still rolls within seconds of you pushing a values change because flux reconcile or a webhook (see notification-controller’s Receiver CRD) fires it manually.
  • The helm-controller stores release state in Secrets. Each HelmRelease produces a sh.helm.release.v1.<name>.<rev> Secret per revision. With historyMax: 10 (default), ten revisions accumulate. Set lower if you have many small HelmReleases — these Secrets count against etcd size.
  • cleanupOnFail is critical. Without it, a failed upgrade leaves the HelmRelease in failed state and the partially-upgraded resources in the cluster. Future reconciles refuse to advance until you manually resolve.
  • HelmRelease deletion deletes the release. Removing the YAML from Git triggers helm uninstall on the next reconcile. If the chart manages a PVC with RetainPolicy: Retain, the PVC stays; everything else is gone. Always confirm this is what you want.
  • Cross-namespace chart sources require an explicit sourceRef.namespace. A HelmRelease in app referencing a HelmRepository in flux-system works only if the namespace is specified — the controller does not search.

Stack Harbor runs Flux-managed Helm releases for clients whose platform stack (ingress, cert-manager, monitoring, secrets operators) is exclusively chart-based — the HelmRepository catalogue, the chart-version pinning policy, the values-vs-valuesFrom split, and the upgrade remediation tuned per workload. See how we deliver that within managed Kubernetes operations.