Helm Plugin (helm/v2-alpha)
The helm/v2-alpha plugin generates Helm charts from your project’s kustomize output, letting you distribute your operator as either a bundle or a Helm chart.
The plugin dynamically builds charts from make build-installer output and preserves your customizations like environment variables, labels, annotations, and security contexts.
Why use Helm
By default, Kubebuilder creates a bundle of manifests:
make build-installer IMG=<registry>/<project-name:tag>
Users install it with:
kubectl apply -f https://raw.githubusercontent.com/<org>/project-v4/<tag-or-branch>/dist/install.yaml
Many users prefer Helm for packaging and upgrades. This plugin converts dist/install.yaml into a Helm chart that mirrors your project.
Features
- Generates charts from kustomize output, not boilerplate
- Preserves environment variables, labels, annotations, and patches
- Organizes templates to match your
config/directory layout - Includes only configurable parameters in
values.yaml - Never overwrites
Chart.yaml; preservesvalues.yaml,NOTES.txt,_helpers.tpl,.helmignore,test-chart.yml,network-policy/allow-metrics-traffic.yaml, andnetwork-policy/allow-webhook-traffic.yamlunless you use--force - Adds default
ServiceMonitorandNetworkPolicytemplates when kustomize output does not provide them - Places custom resources in
templates/extras/with Helm templating
Usage
Basic workflow
Create a project and build the installer bundle:
kubebuilder init
make build-installer IMG=<registry>/<project:tag>
Generate the Helm chart from kustomize output:
kubebuilder edit --plugins=helm/v2-alpha
To regenerate preserved files (except Chart.yaml), use --force:
kubebuilder edit --plugins=helm/v2-alpha --force
Advanced options
Use a custom manifests file:
kubebuilder edit --plugins=helm/v2-alpha --manifests=manifests/custom-install.yaml
Write chart to a custom output directory:
kubebuilder edit --plugins=helm/v2-alpha --output-dir=charts
Combine custom manifests and output directory:
kubebuilder edit --plugins=helm/v2-alpha \
--manifests=manifests/install.yaml \
--output-dir=helm-charts
Chart structure
The plugin generates a chart layout that mirrors your config/ directory:
<output-dir>/chart/
├── Chart.yaml
├── values.yaml
├── .helmignore
└── templates/
├── NOTES.txt
├── _helpers.tpl
├── rbac/ # Individual RBAC files (examples)
│ ├── controller-manager.yaml
│ ├── leader-election-role.yaml
│ ├── leader-election-rolebinding.yaml
│ ├── manager-role.yaml
│ ├── manager-rolebinding.yaml
│ ├── metrics-auth-role.yaml
│ ├── metrics-auth-rolebinding.yaml
│ ├── metrics-reader.yaml
│ └── ...
├── crd/ # Individual CRD files (examples)
│ ├── busyboxes.example.com.testproject.org.yaml
│ └── ...
├── cert-manager/
│ ├── metrics-certs.yaml
│ ├── selfsigned-issuer.yaml
│ └── serving-cert.yaml
├── manager/
│ └── manager.yaml
├── metrics/
│ └── controller-manager-metrics-service.yaml
├── webhook/
│ ├── validating-webhook-configuration.yaml
│ └── webhook-service.yaml
├── monitoring/
│ └── servicemonitor.yaml
├── network-policy/
│ ├── allow-metrics-traffic.yaml
│ └── allow-webhook-traffic.yaml # If webhooks are configured
└── extras/ # Custom resources (if any)
├── my-service.yaml
└── my-config.yaml
Values configuration
The generated values.yaml provides configuration options extracted from your actual deployment.
Namespace creation is not managed by the chart; use Helm’s --namespace and --create-namespace flags when installing.
How values are formatted
Values are uncommented when:
- Extracted from kustomize source manifests
- Standard Helm fields (replicas, image, resource names)
Values stay commented when:
- Optional Kubernetes features not in use (imagePullSecrets, priorityClassName)
- Advanced configuration not needed for basic usage (topology spread, pod disruption budget)
- User customization fields (name overrides, custom labels)
Example:
## String to partially override chart.fullname template (will maintain the release name)
##
# nameOverride: ""
## String to fully override chart.fullname template
##
# fullnameOverride: ""
## Configure the controller manager deployment
##
manager:
## Set to false to skip manager installation
##
enabled: true
replicas: 1
image:
repository: controller
## Image tag (defaults to Chart.appVersion if not set)
##
# tag: ""
pullPolicy: IfNotPresent
## Arguments
##
args:
- --leader-elect
## Health probes.
## The manager serves the liveness (/healthz) and readiness (/readyz) endpoints on this port.
##
healthProbe:
# Health probe server port
port: 8081
## Image pull secrets
##
# imagePullSecrets:
# - name: myregistrykey
## Pod-level security settings
##
podSecurityContext:
runAsNonRoot: true
seccompProfile:
type: RuntimeDefault
## Container-level security settings
##
securityContext:
allowPrivilegeEscalation: false
capabilities:
drop:
- ALL
readOnlyRootFilesystem: true
## Resource limits and requests
##
resources:
limits:
cpu: 500m
memory: 128Mi
requests:
cpu: 10m
memory: 64Mi
## Manager pod's affinity
##
affinity: {}
## Manager pod's node selector
##
nodeSelector: {}
## Manager pod's tolerations
##
tolerations: []
## Deployment strategy
##
# strategy:
# type: RollingUpdate
# rollingUpdate:
# maxSurge: 25%
# maxUnavailable: 25%
## Priority class name
##
# priorityClassName: ""
## Topology spread constraints
##
# topologySpreadConstraints: []
## Termination grace period seconds
##
terminationGracePeriodSeconds: 10
## Custom Deployment labels
##
# labels: {}
## Custom Deployment annotations
##
# annotations: {}
## Custom Pod labels and annotations
##
# pod:
# labels: {}
# annotations: {}
## RBAC configuration
##
rbac:
## RBAC resource scope
## - false (default): ClusterRole/ClusterRoleBinding (all namespaces)
## - true: Role/RoleBinding (release namespace only)
##
namespaced: false
## Helper roles for CRD management (admin/editor/viewer)
##
helpers:
## Install convenience admin/editor/viewer roles for CRDs
##
enabled: false
## ServiceAccount configuration
##
serviceAccount:
# Install default ServiceAccount provided
enabled: true
## Existing ServiceAccount name (required when enabled=false)
## Set to "default" to use the namespace default ServiceAccount
## Note: When enabled=true, respects nameOverride/fullnameOverride
##
# name: ""
## Custom ServiceAccount annotations
##
# annotations: {}
## Custom ServiceAccount labels
##
# labels: {}
## Custom Resource Definitions
##
crd:
# Install CRDs with the chart
enabled: true
# Keep CRDs when uninstalling
keep: true
## Controller metrics endpoint.
## Enable to expose /metrics endpoint
##
metrics:
enabled: true
# Metrics server port
port: 8443
# Enable secure metrics: HTTPS with certs/auth (true) or HTTP (false).
# Note: Metrics authn/authz needs ClusterRole access.
secure: true
## Cert-manager integration for TLS certificates.
## Required for webhook certificates and metrics endpoint certificates.
##
certManager:
enabled: false
## Prometheus ServiceMonitor for metrics scraping.
## Requires prometheus-operator to be installed in the cluster.
##
prometheus:
enabled: false
## Network policies for controlling traffic flow.
## Enable to restrict ingress to the controller manager.
##
networkPolicy:
enabled: false
Installation
The plugin adds Helm targets to your Makefile:
make helm-deploy IMG=<registry>/<project:tag>
make helm-status
Install manually with all features enabled:
helm install my-release ./dist/chart --namespace my-project-system --create-namespace
Install only CRDs and RBAC:
helm install my-release ./dist/chart --set manager.enabled=false --set webhook.enabled=false
Install without webhooks:
helm install my-release ./dist/chart --set webhook.enabled=false --set certManager.enabled=false
Install with NetworkPolicy resources:
helm install my-release ./dist/chart --set networkPolicy.enabled=true
Extra volumes
Add volumes and volume mounts to the manager deployment beyond webhook and metrics certificates.
Volumes in your kustomize configuration (config/manager/manager.yaml or patches) are written to the chart template. When the manager has extra volumes, values.yaml includes manager.extraVolumes and manager.extraVolumeMounts fields. Use these to add more volumes at install time.
Webhook and metrics certificates (webhook-certs, metrics-certs) are managed separately and controlled by certManager.enabled and (for metrics TLS) metrics.enabled + metrics.secure.
Metrics configuration
metrics.port
Set metrics.port to change the port used by the metrics endpoint. The chart applies the same value to the manager --metrics-bind-address argument, the metrics Service port and targetPort, and the metrics NetworkPolicy.
For example, install the chart with the metrics endpoint on port 8444:
helm install my-operator ./dist/chart --set metrics.port=8444
The default is 8443, detected from your project configuration.
metrics.secure
Control transport security and authentication for the metrics endpoint (default: true).
When true:
- Uses HTTPS with TLS certificates (when
certManager.enabled=true) - Creates
metrics-auth-roleClusterRole for authentication - ServiceMonitor uses HTTPS
When false:
- Uses HTTP without authentication
- No TLS certificates
- ServiceMonitor uses HTTP
Webhook port configuration
Set webhook.port to change the port used by the manager webhook server. The chart applies the same value to the manager argument, container port, webhook service, and webhook NetworkPolicy.
For example, install the chart with the webhook server on port 9444:
helm install my-operator ./dist/chart --set webhook.port=9444
The default is 9443, detected from your project configuration.
Health probe port configuration
Set manager.healthProbe.port to change the port where the manager serves its health probes. The liveness (/healthz) and readiness (/readyz) endpoints bind to this port. The chart applies the same value to the --health-probe-bind-address argument, the health container port, and the httpGet port of both probes.
For example, install the chart with the health probes on port 8082:
helm install my-operator ./dist/chart --set manager.healthProbe.port=8082
The default is 8081, detected from your project configuration.
Passing args for the manager
Use manager.args in values.yaml to pass extra flags to the manager container that the chart does not expose as dedicated values. The chart renders each entry through Helm’s tpl function, evaluated against the chart’s root context, so an arg can reference other values, release information, or chart template functions instead of only a static string.
For example, set the leader election namespace from the release namespace and add a plain flag:
manager:
args:
- --leader-election-namespace={{ .Release.Namespace }}
- --leader-elect
Helm evaluates {{ .Release.Namespace }} at render time, so the manager container receives --leader-election-namespace=<release-namespace>. The --leader-elect flag has no template syntax, so it renders unchanged.
NetworkPolicy configuration
Set networkPolicy.enabled: true to install NetworkPolicy resources for the manager pod.
When the kustomize output includes NetworkPolicy resources, the plugin converts them into chart templates and sets networkPolicy.enabled: true. When no NetworkPolicy resources are present in the kustomize output, the plugin generates default templates for metrics traffic, and also for webhook traffic when webhooks are detected in the provided kustomize input files.
Custom labels and annotations
Add custom labels and annotations using manager.labels, manager.annotations, manager.pod.labels, and manager.pod.annotations. Duplicate keys from kustomize are filtered automatically.
ServiceAccount configuration
Set serviceAccount.enabled: true (default) to create a ServiceAccount. Set serviceAccount.enabled: false to use an existing one:
serviceAccount:
enabled: false
name: my-existing-sa
The chart ships with the toggle enabled:
serviceAccount:
enabled: true
Helm merges this default into any values file that omits the section, so omitting it keeps creating the account.
When serviceAccount.enabled: false, serviceAccount.name is required and the chart fails to render without it. Set name: default explicitly to use the namespace default ServiceAccount.
The resolved name is used consistently in the Deployment and in all RBAC bindings, for both cluster-scoped and namespaced RBAC modes:
serviceAccount.enabled | serviceAccount.name | ServiceAccount created | Name used (Deployment + RBAC bindings) |
|---|---|---|---|
true (default) | any | Yes | Generated (<fullname>-controller-manager, name is ignored) |
false | set | No | The provided name |
false | default | No | Namespace default ServiceAccount |
false | unset | No | Render fails with a clear error |
The generated name is built from the chart fullname, typically <release>-<chart>, plus the -controller-manager suffix, truncated to the 63-character Kubernetes limit. It respects nameOverride and fullnameOverride.
Add annotations for cloud provider integrations:
serviceAccount:
enabled: true
annotations:
iam.gke.io/gcp-service-account: my-operator@project.iam.gserviceaccount.com
External ServiceAccount names are used as-is and ignore nameOverride or fullnameOverride.
RBAC configuration
rbac.namespaced
Set the scope of RBAC permissions:
false(default): ClusterRole and ClusterRoleBinding for all namespacestrue: Role and RoleBinding for release namespace only
rbac.roleNamespaces
When your kustomize output includes Roles and RoleBindings for specific namespaces (other than the manager namespace), the plugin automatically detects them and creates roleNamespaces entries.
Add namespace-specific RBAC markers to your controller:
// +kubebuilder:rbac:groups=apps,namespace=infrastructure,resources=deployments,verbs=get;list;watch
// +kubebuilder:rbac:groups="",namespace=users,resources=secrets,verbs=get;list;watch
Run make manifests to generate the RBAC manifests, then run the plugin. The generated values.yaml includes:
rbac:
namespaced: false
## Namespace configuration for Roles deployed to namespaces different from the manager namespace
## Keys are resource name suffixes (without project prefix)
##
roleNamespaces:
# RBAC resource manager-role-infrastructure deploys to namespace infrastructure
"manager-role-infrastructure": "infrastructure"
# RBAC resource manager-rolebinding-infrastructure deploys to namespace infrastructure
"manager-rolebinding-infrastructure": "infrastructure"
# RBAC resource manager-role-users deploys to namespace users
"manager-role-users": "users"
# RBAC resource manager-rolebinding-users deploys to namespace users
"manager-rolebinding-users": "users"
helpers:
enabled: false
Override namespaces at deployment using a values file:
# custom-values.yaml
rbac:
roleNamespaces:
"manager-role-infrastructure": "prod-infra"
"manager-rolebinding-infrastructure": "prod-infra"
"manager-role-users": "prod-users"
"manager-rolebinding-users": "prod-users"
Install with custom namespaces:
helm install my-operator ./dist/chart -f custom-values.yaml
Or use --set:
helm install my-operator ./dist/chart \
--set 'rbac.roleNamespaces[manager-role-infrastructure]=prod-infra' \
--set 'rbac.roleNamespaces[manager-role-users]=prod-users'
Flags
| Flag | Description |
|---|---|
| –manifests | Path to YAML file containing Kubernetes manifests (default: dist/install.yaml) |
| –output-dir string | Output directory for chart (default: dist) |
| –force | Regenerates preserved files except Chart.yaml (values.yaml, NOTES.txt, _helpers.tpl, .helmignore, test-chart.yml, network-policy/allow-metrics-traffic.yaml, network-policy/allow-webhook-traffic.yaml) |