Helm’s templating engine can generate RBAC roles and ServiceAccounts, but the real magic isn’t just generating YAML; it’s dynamically scoping permissions based on your deployment’s needs.

Let’s see it in action. Imagine you have a Helm chart for a web application that needs to read secrets from Kubernetes.

# templates/rbac.yaml
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRole
metadata:

  name: {{ include "my-app.fullname" . }}-reader

rules:
  - apiGroups: [""]
    resources: ["secrets"]
    verbs: ["get", "list", "watch"]
---
apiVersion: v1
kind: ServiceAccount
metadata:

  name: {{ include "my-app.fullname" . }}

  labels:

    {{- include "my-app.labels" . | nindent 4 }}

---
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRoleBinding
metadata:

  name: {{ include "my-app.fullname" . }}-reader-binding

subjects:
  - kind: ServiceAccount

    name: {{ include "my-app.fullname" . }}


    namespace: {{ .Release.Namespace }}

roleRef:
  kind: ClusterRole

  name: {{ include "my-app.fullname" . }}-reader

  apiGroup: rbac.authorization.k8s.io

When you helm install my-release ./my-app-chart, Helm processes this template. If your chart’s name is my-app and the release name is my-release, the generated resources would look something like this:

# Generated YAML
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRole
metadata:
  name: my-release-my-app-reader
rules:
  - apiGroups: [""]
    resources: ["secrets"]
    verbs: ["get", "list", "watch"]
---
apiVersion: v1
kind: ServiceAccount
metadata:
  name: my-release-my-app
  labels:
    app.kubernetes.io/name: my-app
    app.kubernetes.io/instance: my-release
    app.kubernetes.io/version: "1.16.0" # Example version
    app.kubernetes.io/component: frontend
    app.kubernetes.io/part-of: my-app
---
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRoleBinding
metadata:
  name: my-release-my-app-reader-binding
subjects:
  - kind: ServiceAccount
    name: my-release-my-app
    namespace: default # Or whatever namespace you installed into
roleRef:
  kind: ClusterRole
  name: my-release-my-app-reader
  apiGroup: rbac.authorization.k8s.io

This setup provides a dedicated ServiceAccount (my-release-my-app) for your application pods and binds it to a ClusterRole (my-release-my-app-reader) that grants read-only access to secrets cluster-wide. This is a common pattern for applications that need to fetch configuration or sensitive data securely.

The {{ include "my-app.fullname" . }} helper is crucial. It generates a unique name for your resources based on the release name and chart name, preventing naming collisions when deploying multiple instances of the same chart or different charts with similar resource names. The {{ .Release.Namespace }} ensures the ServiceAccount is correctly referenced within its own namespace.

You can make this even more dynamic. Suppose your application has different deployment tiers (e.g., read-only, read-write, admin). You can use values.yaml to control which RBAC resources are generated and what permissions they have.

# values.yaml
rbac:
  create: true
  readSecrets: true
  writeSecrets: false

serviceAccount:
  create: true

And in your templates/rbac.yaml:


{{- if .Values.rbac.create }}


{{- if .Values.rbac.readSecrets }}

---
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRole
metadata:

  name: {{ include "my-app.fullname" . }}-secret-reader

rules:
  - apiGroups: [""]
    resources: ["secrets"]
    verbs: ["get", "list", "watch"]
---
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRoleBinding
metadata:

  name: {{ include "my-app.fullname" . }}-secret-reader-binding

subjects:
  - kind: ServiceAccount

    name: {{ include "my-app.fullname" . }}


    namespace: {{ .Release.Namespace }}

roleRef:
  kind: ClusterRole

  name: {{ include "my-app.fullname" . }}-secret-reader

  apiGroup: rbac.authorization.k8s.io

{{- end }}


{{- if .Values.rbac.writeSecrets }}

---
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRole
metadata:

  name: {{ include "my-app.fullname" . }}-secret-writer

rules:
  - apiGroups: [""]
    resources: ["secrets"]
    verbs: ["get", "list", "watch", "create", "update", "patch", "delete"]
---
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRoleBinding
metadata:

  name: {{ include "my-app.fullname" . }}-secret-writer-binding

subjects:
  - kind: ServiceAccount

    name: {{ include "my-app.fullname" . }}


    namespace: {{ .Release.Namespace }}

roleRef:
  kind: ClusterRole

  name: {{ include "my-app.fullname" . }}-secret-writer

  apiGroup: rbac.authorization.k8s.io

{{- end }}


{{- end }}



{{- if .Values.serviceAccount.create }}

apiVersion: v1
kind: ServiceAccount
metadata:

  name: {{ include "my-app.fullname" . }}

  labels:

    {{- include "my-app.labels" . | nindent 4 }}


{{- end }}

With this, you can install with helm install my-release ./my-app-chart --set rbac.writeSecrets=true to grant write access, or omit rbac.create=true entirely if your application doesn’t need any special permissions and will use the default ServiceAccount.

The truly powerful aspect is how Helm’s templating allows you to define granular RBAC policies that adapt to your application’s runtime requirements. You’re not just writing static YAML; you’re defining a policy generation engine. This means you can create charts that automatically provision the exact permissions needed for a given deployment, adhering to the principle of least privilege without manual intervention. This is particularly useful in multi-tenant environments or for complex microservice architectures where precise control over inter-service communication and data access is paramount. You can even use Helm’s lookup function (though it’s often discouraged for production due to performance and dependency issues) to dynamically fetch information from the cluster and influence RBAC generation, though usually, this is handled by external controllers or admission webhooks.

When you define rules for a Role or ClusterRole, the apiGroups field is a list of API groups the rule applies to. An empty string "" in apiGroups signifies the core Kubernetes API group, which includes resources like Pods, Services, and Secrets. This is a common point of confusion: forgetting the "" means your rule won’t match core resources.

Want structured learning?

Take the full Helm course →