K3s’s Helm Controller is a surprisingly powerful tool that lets you manage Helm chart deployments directly from Kubernetes manifests, bypassing the need for manual helm install or helm upgrade commands.
Let’s see it in action. Imagine you want to deploy the popular ingress-nginx chart. Instead of running helm install ingress-nginx ingress-nginx/ingress-nginx --namespace ingress-nginx --create-namespace, you create a HelmChart resource in Kubernetes:
apiVersion: helm.cattle.io/v1
kind: HelmChart
metadata:
name: ingress-nginx
namespace: ingress-nginx
spec:
chart: ingress-nginx/ingress-nginx
repo: https://ingress-nginx.github.io/charts
version: 4.2.0
targetNamespace: ingress-nginx
createNamespace: true
values: |
controller:
replicaCount: 2
service:
annotations:
service.beta.kubernetes.io/aws-load-balancer-type: nlb
When you apply this YAML (kubectl apply -f your-helmchart.yaml), the K3s Helm Controller, which is a built-in component of K3s, watches for HelmChart resources. Upon detecting this new resource, it essentially performs the helm install command for you in the background, using the specified chart, version, repository, and values. It even creates the ingress-nginx namespace if it doesn’t exist, thanks to createNamespace: true.
The mental model here is that you’re treating Helm charts as first-class Kubernetes objects. The Helm Controller acts as a reconciliation loop: it ensures that the state defined in your HelmChart resource matches the deployed Helm release. If you update the version in the HelmChart resource, the controller will automatically trigger a helm upgrade. If you change a value in the values block, it will also perform an upgrade. If you delete the HelmChart resource, the controller will execute helm uninstall.
The problem this solves is the automation of Helm deployments within a Kubernetes-native workflow. You can now version control your application deployments, including their dependencies managed by Helm, using standard GitOps practices. This means your entire application stack, from Kubernetes resources to Helm-managed applications, can be managed declaratively.
The targetNamespace field is crucial. While the HelmChart resource itself lives in a Kubernetes namespace (in the example above, ingress-nginx), targetNamespace specifies where the actual Helm chart resources (Deployments, Services, etc.) will be installed. If createNamespace is true and targetNamespace is specified, the controller will create that namespace. If createNamespace is false, the targetNamespace must already exist.
The values block accepts a multi-line YAML string. This is where you provide all the configuration options for your Helm chart, just as you would with a values.yaml file or the --set flag in a manual Helm command. These values are passed directly to Helm during the install or upgrade process.
One detail that often trips people up is how the controller handles chart repositories. You need to specify the repo URL. The controller then uses this URL to fetch the Helm chart. If you’re using a private Helm repository, you’ll need to configure K3s or the controller to authenticate with that repository, typically by providing credentials in a Kubernetes secret that the Helm Controller can access. Without proper authentication, fetching charts from private repositories will fail.
The next concept you’ll likely explore is managing multiple Helm charts in a single K3s cluster and how to handle dependencies between them using the dependsOn field within the HelmChart resource.