30.4 HelmRelease: Managing Helm Releases Declaratively
Right, so you’ve got your cluster bootstrapped with Flux. It’s happily syncing your Kustomization resources, and you feel like you’ve got a handle on this whole GitOps thing. But let’s be real: you’re probably not just deploying raw YAML. You’re almost certainly using Helm charts, either your own or, more likely, a bunch from the community. Manually running helm install or helm upgrade is a hard no-go in our new GitOps utopia. It’s a imperative speck in our declarative masterpiece. This is where the HelmRelease comes in to save the day, and your sanity.
The HelmRelease is a Flux custom resource (kind: HelmRelease) that tells the Helm Controller, “Hey, see this Helm chart? I want this specific version of it, deployed with these exact values, and I want you to keep it that way forever until I say otherwise.” You define the desired state in a YAML file, commit it, and Flux makes it happen. It’s the declarative API helm always wished it had.
The Basic Anatomy of a HelmRelease
Let’s break down a simple but functional example. We’ll deploy the trusty ingress-nginx chart, because everyone needs ingress.
apiVersion: helm.toolkit.fluxcd.io/v2beta1
kind: HelmRelease
metadata:
name: ingress-nginx
namespace: infra
spec:
interval: 5m0s
chart:
spec:
chart: ingress-nginx
version: "4.8.2"
sourceRef:
kind: HelmRepository
name: ingress-nginx
namespace: flux-system
interval: 1m0s
values:
controller:
service:
type: LoadBalancer
annotations:
service.beta.kubernetes.io/aws-load-balancer-type: "nlb"
There’s a lot to unpack here. The interval in the main spec is how often Flux will check if this release is actually in the state you defined. The chart.spec is where the magic happens: it points to a HelmRepository source (which you should have already defined) that tells Flux where to pull the chart from. Notice the separate interval here? That’s how often Flux will check the repository itself for a new version of the chart. This separation is brilliant—you can check for new chart versions every minute without necessarily reconciling the release itself every 60 seconds.
Sourcing Charts: The Big Three Ways
You’re not limited to public repositories. Flux is wonderfully flexible here, which is code for “there are a few ways to do this, and you need to know the trade-offs.”
1. From a HelmRepository (Public or Private):
This is the standard way, as shown above. You define a HelmRepository resource that points to a URL. It works with any classic chart museum, including private ones that need credentials.
2. From a GitRepository (Charts in Git): This is my personal favorite for internal charts. You store your chart in a Git repo (e.g., alongside the application code it deploys). Flux can pull the chart directly from Git, which feels truly GitOps-native.
chart:
spec:
chart: ./charts/my-app
sourceRef:
kind: GitRepository
name: my-app-repo
namespace: flux-system
interval: 1m0s
3. From an OCI Repository: This is the future, and it’s quickly becoming the present. You can store Helm charts in OCI registries like Docker Hub, GHCR, ECR, or GAR. It’s more efficient and often simpler for CI/CD pipelines.
chart:
spec:
chart: podinfo
version: "6.4.0"
sourceRef:
kind: HelmRepository
name: podinfo-oci
namespace: flux-system
---
apiVersion: source.toolkit.fluxcd.io/v1beta2
kind: HelmRepository
metadata:
name: podinfo-oci
namespace: flux-system
spec:
interval: 5m
url: oci://ghcr.io/stefanprodan/charts
The Art of the values.yaml Dance
You wouldn’t run helm install without a custom values.yaml, and you shouldn’t with HelmRelease either. You have two primary options, and which one you choose says a lot about how you organize your GitOps repo.
Option A: Inline everything. Just stick your values right in the HelmRelease under the values: key. It’s simple, and everything is in one file. This is great for simple configurations or if you’re a masochist who enjoys 1000-line YAML files.
Option B: External values files. This is the clean way. You can point to a separate values file stored in the same Git repository. This keeps your HelmRelease manifest tidy and lets you potentially share values files between releases.
spec:
...
valuesFrom:
- kind: ConfigMap
name: my-app-common-values
- kind: Secret
name: my-app-secret-values
- kind: ConfigMap
name: env-specific-values
Wait, what? ConfigMap and Secret? Yes. This is Flux’s superpower. Your values don’t have to be flat files in Git. They can come from external sources like ConfigMaps and Secrets that Flux itself manages through other Kustomization resources. This is how you handle environment-specific or sensitive values without hardcoding them into your application’s deployment manifest. It’s a bit more setup, but the separation of concerns is worth its weight in gold.
Drift Detection and Remediation: The Whole Point
Here’s the killer feature: true drift detection. If someone goes into the cluster and does a kubectl edit on a resource managed by your HelmRelease, Flux will notice. On its next interval tick, it will compute a diff and revert the change to match what’s defined in your values. This is the “Ops” in GitOps. It enforces immutability and makes your cluster state predictable. You can see this in action by looking at the kustomization-controller logs after you make a rogue edit. It’s a thing of beauty.
Common Pitfalls and “Oh, C’mon” Moments
- The Chart Source Ref Gotcha: The
sourceRefin yourchart.specmust point to aHelmRepositoryorGitRepositorythat exists and is ready. If you apply yourHelmReleasebefore the source is ready, the release will just sit there with a'Chart' not founderror. Checkkubectl get helmrepositoriesfirst. - The API Version Trap: You’ll mostly use
apiVersion: helm.toolkit.fluxcd.io/v2beta1. If you find older documentation usingv2alpha1or especiallyv1, run away. The API changed significantly. - Values Merging Logic: When you use multiple
valuesFromsources, they are merged in order. Later keys override earlier ones. This is powerful but can be confusing if you’re not careful. The inlinevalues:are applied last and have the highest precedence. Draw a map if you have to. - The Install vs. Upgrade Quirk: Unlike the
helmCLI, aHelmReleasedoesn’t have an explicitinstallcommand. You just define the release. If it doesn’t exist, Flux installs it. If it does, Flux upgrades it. This is the declarative way, but it means you need to be extra sure yourvaluesare correct on the first apply, as there’s no--dry-runflag on the resource itself (though you can usekubectl diffif you have it configured).
The HelmRelease is the workhorse that makes GitOps practical for real-world applications. It takes the wild west of helm commands and turns it into a disciplined, auditable, and repeatable process. Master this, and you’ve mastered the most common pattern for deploying software with Flux.