> ## Documentation Index
> Fetch the complete documentation index at: https://notes.kodekloud.com/llms.txt
> Use this file to discover all available pages before exploring further.

# Demo Create A Helm Based Operator

> Guide demonstrating how to build a Helm-based Kubernetes Operator with Operator SDK, mapping custom resources to Helm charts to manage install, upgrades, and uninstall without writing Go reconciler.

In this lesson we wrap a Helm chart with an Operator using the Operator SDK Helm plugin. The goal is to avoid writing a Go reconciler: the Operator SDK will scaffold a controller that watches a Custom Resource (CR) and uses Helm to install, upgrade, and uninstall chart releases.

<Frame>
  <img src="https://mintcdn.com/kodekloud-c4ac6d9a/NE1cIQtF4gs6qQ4D/images/Kubernetes-Operators/Alternative-Approaches-Operator-SDK-Helm-Operator/Demo-Create-A-Helm-Based-Operator/create-helm-operator-demo-slide.jpg?fit=max&auto=format&n=NE1cIQtF4gs6qQ4D&q=85&s=ea1545a19163c6f3597472c94ed15560" alt="The image is a presentation slide featuring the text &#x22;Create A Helm Based Operator&#x22; and &#x22;Demo&#x22; with a minimalistic design. It also includes a copyright credit to KodeKloud." width="1920" height="1080" data-path="images/Kubernetes-Operators/Alternative-Approaches-Operator-SDK-Helm-Operator/Demo-Create-A-Helm-Based-Operator/create-helm-operator-demo-slide.jpg" />
</Frame>

## What you get from a Helm-based Operator

A Helm-based operator uses Helm to manage the application lifecycle. Instead of writing reconciliation logic in Go, you map a Kubernetes API (CRD) to a Helm chart. The Operator SDK Helm plugin generates a smaller project surface than the Go plugin: you get a Helm chart, a `watches.yaml` mapping, and Kustomize manifests under `config`. The operator reconciles CRs by rendering the chart with values derived from the CR `spec`.

Key benefits:

* No custom Go reconciler required.
* Leverages Helm charts and existing chart life-cycle semantics (install/upgrade/uninstall).
* CR `spec` fields are passed into the chart as Helm values.

Recommended reading:

* Operator SDK docs: [https://sdk.operatorframework.io](https://sdk.operatorframework.io)
* Helm docs: [https://helm.sh](https://helm.sh)
* Kubernetes CRD overview: [https://kubernetes.io/docs/concepts/extend-kubernetes/api-extension/custom-resources/](https://kubernetes.io/docs/concepts/extend-kubernetes/api-extension/custom-resources/)

## Initialize a Helm-based Operator project

Start in a clean directory and initialize an Operator SDK project with the Helm plugin:

```bash theme={null}
operator-sdk init --plugins=helm --domain=example.com
```

Example output:

```text theme={null}
INFO[0000] Writing kustomize manifests for you to edit...
Next: define a resource with:
$ operator-sdk create api
```

## Create the API surface (CRD + chart for the resource)

Create an API for a sample NGINX resource using group `demo`, version `v1`, and kind `Nginx`. This command generates a Helm chart under `helm-charts/nginx` and scaffolds the sample CR manifest:

```bash theme={null}
operator-sdk create api --group=demo --version=v1 --kind=Nginx
```

Example output:

```text theme={null}
INFO[0000] Writing kustomize manifests for you to edit...
Created helm-charts/nginx
Generating RBAC rules
WARN[0000] The RBAC rules generated in config/rbac/role.yaml are based on the chart's default manifest. Some rules may be missing for resources that are only enabled with custom values, and some existing rules may be overly broad. Double check the rules generated in config/rbac/role.yaml to ensure they meet the operator's permission requirements.
```

<Callout icon="warning" color="#FF6B6B">
  The RBAC rules are derived from the chart's default manifests. Review `config/rbac/role.yaml` and tighten permissions if needed before deploying to production.
</Callout>

## How the mapping works: watches.yaml

The `watches.yaml` file maps the Kubernetes API to the chart:

* If you see a `Nginx` object in the API group `demo.example.com`, reconcile it using the chart in `helm-charts/nginx`.
* The operator reads the CR `spec` and injects those values into Helm rendering.

This mapping is the core of a Helm-based operator: CR → Helm values → rendered Kubernetes resources.

## Chart design and values

Design your Helm chart to expose the values you want users to control via the CR `spec`. Fields under `spec` in the CR are translated into Helm chart values, so chart authors should treat these values as the operator-facing surface.

Example snippets from the generated chart's `values.yaml` (these become the operator surface):

```yaml theme={null}
podLabels: {}

podSecurityContext: {}
  # fsGroup: 2000

securityContext: {}
  # capabilities:
  #   drop:
  #     - ALL
  # readOnlyRootFilesystem: true
  # runAsNonRoot: true
  # runAsUser: 1000
```

<Callout icon="lightbulb" color="#1CB2FE">
  Design your Helm chart with operator usage in mind: expose the values you expect operators (or users) to modify via the custom resource `spec`.
</Callout>

Common example values that the generated chart exposes:

| Value key          | Purpose                                   | Example                                                           |
| ------------------ | ----------------------------------------- | ----------------------------------------------------------------- |
| `replicaCount`     | Controls Deployment replica count         | `replicaCount: 1`                                                 |
| `image`            | Container image configuration             | `yaml image: repository: nginx pullPolicy: IfNotPresent tag: "" ` |
| `imagePullSecrets` | Image pull secrets for private registries | `imagePullSecrets: []`                                            |

## Install the CRD and run the operator locally

Install the generated CRD so the API server recognizes the `Nginx` resource type:

```bash theme={null}
make install
# Example of what make install runs:
# kustomize build config/crd | kubectl apply -f -
```

Example output:

```text theme={null}
customresourcedefinition.apiextensions.k8s.io/nginxes.demo.example.com created
```

Start the operator in the foreground (leave this running in one terminal):

```bash theme={null}
make run
```

Example operator logs when starting:

```json theme={null}
{"level":"info","ts":"2026-06-16T11:34:35+02:00","logger":"controller-runtime.metrics","msg":"Serving metrics server","bindAddress":":8080","secure":false}
{"level":"info","ts":"2026-06-16T11:34:35+02:00","msg":"Starting EventSource","controller":"nginx-controller","source":"kind source: unstructured.Unstructured{}"}
{"level":"info","ts":"2026-06-16T11:34:35+02:00","msg":"Starting Controller","controller":"nginx-controller"}
{"level":"info","ts":"2026-06-16T11:34:35+02:00","msg":"Starting workers","controller":"nginx-controller","worker count":16}
```

## Create a release by applying the sample CR

In another terminal, apply the generated sample custom resource that represents an NGINX release:

```bash theme={null}
kubectl apply -f config/samples/demo_v1_nginx.yaml
```

Example output:

```text theme={null}
nginxes.demo.example.com/nginx-sample created
```

What happens:

* The controller sees the new `Nginx` object.
* It reads `.spec` from the CR and passes those values into Helm rendering.
* Helm templates are rendered and the resulting Kubernetes resources (Deployment, Service, ConfigMap, etc.) are created.

No custom Go reconciler code is needed; the operator uses Helm to manage the release lifecycle.

## Verify the release and troubleshoot

Wait for the generated deployment to become available:

```bash theme={null}
kubectl wait --for=condition=Available deploy/nginx-sample --timeout=120s
```

Inspect replicas and ready replicas:

```bash theme={null}
kubectl get deploy nginx-sample -o jsonpath='{.spec.replicas}{"/"}{.status.readyReplicas}{"\n"}'
```

List resources associated with this Helm release using the instance label:

```bash theme={null}
kubectl get deploy -l app.kubernetes.io/instance=nginx-sample
```

If you need to inspect the full generated Deployment:

```bash theme={null}
kubectl get deploy nginx-sample -o yaml
```

## Update desired state by patching the CR

To change desired state, patch the custom resource. For example, update the replica count:

```bash theme={null}
kubectl patch nginx nginx-sample --type=merge -p '{"spec":{"replicaCount":4}}'
```

The Helm operator will reconcile the change and upgrade the Helm release. Confirm the deployment scaled:

```bash theme={null}
kubectl get deploy nginx-sample -o json
```

## Delete the release by deleting the CR

To uninstall the release, delete the custom resource:

```bash theme={null}
kubectl delete nginx nginx-sample
```

The operator will uninstall the associated Helm release and clean up API objects created by the chart.

## Summary and when to choose Helm vs Go operators

Helm-based operators are ideal when:

* You already have a Helm chart that represents the application lifecycle.
* The CR only needs to expose chart values (mapping `spec` → Helm values).
* You want to avoid writing reconciliation code.

Consider a Go-based controller when:

* You need advanced reconciliation logic.
* You must call external APIs or implement complex status updates.
* Custom status computation or multi-resource coordination is required.

Useful references:

* Operator SDK (Helm plugin): [https://sdk.operatorframework.io/docs/helm/overview/](https://sdk.operatorframework.io/docs/helm/overview/)
* Helm: [https://helm.sh](https://helm.sh)
* Kubernetes CRDs: [https://kubernetes.io/docs/tasks/extend-kubernetes/custom-resources/custom-resource-definitions/](https://kubernetes.io/docs/tasks/extend-kubernetes/custom-resources/custom-resource-definitions/)

<CardGroup>
  <Card title="Watch Video" icon="video" cta="Learn more" href="https://learn.kodekloud.com/user/courses/kubernetes-operators/module/d2537d70-4008-4d53-ad04-b7731ca0f7c0/lesson/d73a38de-90cd-446d-9c96-0653e88167c8" />
</CardGroup>
