Using K3s’ Helm Controller to Deploy Charts via CRDs
Learn how K3s’ Helm Controller lets you declare Helm charts as Kubernetes resources, how to verify its operation, and what trade‑offs to consider for edge workloads.
11 Aug 2025, 04:32 UTC

Problem: Managing Helm charts on a lightweight K3s edge cluster
You run a K3s cluster on a small VM or an IoT gateway and want to install applications with Helm. Adding a separate Tiller server is unnecessary and adds overhead, but you also prefer not to manage external Helm binaries for every deployment. You need a solution that fits K3s’ minimal footprint, works with the cluster’s existing security model, and lets you define chart releases as Kubernetes objects.
Thesis: K3s ships a Helm Controller that watches HelmChart custom resources
Instead of an in‑cluster Helm server (which never existed in Helm v3), K3s deploys a lightweight helm-controller pod in the kube-system namespace. This controller implements the HelmChart CRD, allowing you to declare a chart release as a Kubernetes manifest. The controller then runs the Helm v3 client internally to render and apply the chart, keeping the control plane consistent with the rest of K3s.
How it works
The helm-controller runs as a Deployment with a single replica. It uses the same service account as the K3s API server, so it inherits the cluster’s RBAC, network policies, and pod‑security policies. When you apply a HelmChart resource, the controller:
- Watches for
HelmChartobjects in all namespaces. - Runs
helm templatelocally to generate Kubernetes manifests. - Applies those manifests using the API server, respecting the namespace and labels defined in the CR.
- Updates the status of the
HelmChartresource to reflect success or failure.
Because the controller is just another pod, it can be secured with the same PodSecurityPolicy or OPA/Gatekeeper rules you apply to other workloads.
Verification steps
- Confirm the controller is running – from any machine with
kubectlconfigured for the K3s cluster:
You should see a pod namedkubectl get pods -n kube-system -l app=helm-controllerhelm-controller-…inRunningstate. - Check that the HelmChart CRD exists:
The output should list the CRD with akubectl get crd helmcharts.helm.cattle.ioNAMEcolumn showinghelmcharts.helm.cattle.io. - Validate the controller version (optional):
Look for a line likekubectl -n kube-system logs deployment/helm-controller | head -5INFO Starting helm-controller vX.Y.Z.
Worked example: Deploying the NGINX Ingress chart via a HelmChart CR
First, create a namespace for the ingress controller (you can skip this if you prefer the default namespace):
kubectl create namespace ingress-nginx
Next, save the following manifest as nginx-ingress.yaml:
apiVersion: helm.cattle.io/v1
kind: HelmChart
metadata:
name: nginx-ingress
namespace: ingress-nginx
spec:
chart: https://github.com/kubernetes/ingress-nginx/releases/download/helm-chart-4.5.5/ingress-nginx-4.5.5.tgz
# Alternatively, you can use an OCI chart:
# chart: oci://registry-1.docker.io/bitnamicharts/nginx-ingress
# version: "1.9.0"
targetNamespace: ingress-nginx
valuesContent: |-
controller:
service:
type: ClusterIP
Apply the manifest:
kubectl apply -f nginx-ingress.yaml
Watch the controller’s status:
kubectl -n ingress-nginx get helmchart nginx-ingress -w
When the READY column becomes True, the chart has been rendered and applied. You can verify the resulting resources:
kubectl -n ingress-nginx get pods,svc
You should see the NGINX ingress controller pod and a ClusterIP service.
Limitations and trade‑offs
- No external Helm API – The
helm-controllerdoes not expose a network endpoint that the Helm CLI can talk to. If you need to runhelm upgradeorhelm uninstallfrom a workstation, you must manage theHelmChartresource viakubectlor a GitOps tool. - Chart source flexibility – The CR supports HTTP(S) tarball URLs and OCI charts, but it does not support chart repositories directly; you must provide the full chart URL or OCI reference.
- Resource overhead – The controller adds roughly 10‑15 MiB of RAM and a small CPU footprint, which is still modest for edge nodes but worth noting on extremely constrained devices.
- Status reporting – The
HelmChartstatus fields are limited; complex release notes or test hooks from Helm v2 are not available.
Practical way to check the result
After applying a HelmChart, run:
kubectl -n <namespace> get helmchart <release-name> -o yaml
Inspect the status.appliedResources list to see which Kubernetes objects were created. If the list is empty or the status.conditions show a failure, check the controller logs:
kubectl -n kube-system logs -l app=helm-controller
Actionable closing
If you are operating a K3s cluster and want to manage Helm charts without adding a separate server, start by verifying that the helm-controller pod and the HelmChart CRD are present. Then declare your chart releases as HelmChart manifests and let the controller handle the rendering and application. For CI/CD pipelines, store these manifests in Git and let a tool like Flux or Argo CD apply them; the controller will converge the cluster to the desired state. Remember to review the RBAC bindings that the controller inherits and tighten them if your security model requires least‑privilege access.
0 replies
A thoughtful contribution can make all the difference. Be the first to share one.