A Complete Guide to Building and Hosting Helm Charts

A Complete Guide to Building and Hosting Helm Charts
Photo by Ian Taylor / Unsplash

If you are managing Kubernetes applications, manually handling dozens of YAML files quickly becomes unscalable. This is where Helm comes in.

In this guide, we will cover what Helm is, how to create a custom chart, and how to host it on your own private OCI-compliant registry running inside your cluster.

1. The Theory: Helm and OCI

What is a Helm Chart?

Think of Helm as the package manager for Kubernetes (like apt for Ubuntu). A Helm Chart is the package. Instead of static YAML files, Helm uses templates. You feed these templates a values.yaml file to dynamically configure your application for different environments (dev, staging, prod) without rewriting the core code.

What is OCI?

OCI stands for the Open Container Initiative. It is the universal standard for container formats. Modern Helm fully supports OCI registries. This means the exact same registry you use to store Docker images (like Docker Hub, Harbor, or a private registry) can also store and distribute your Helm charts.

2. Creating Your First Helm Chart

We will start by generating a boilerplate chart and customizing it.

Step 1: Generate the Chart

Use the Helm CLI to create a scaffolding directory named my-app.

Bash

helm create my-app

Step 2: Customize the Chart

Navigate into the my-app directory. Open the values.yaml file to declare your variables. For example, to run two instances of your application:

YAML

# values.yaml
replicaCount: 2

image:
  repository: nginx
  pullPolicy: IfNotPresent
  tag: "latest"

Helm will inject these values into the templates located in the /templates directory during deployment.

Step 3: Lint and Test

Verify your chart has no syntax errors and perform a dry run to see the rendered YAML:

Bash

helm lint ./my-app
helm template ./my-app

3. Setting Up a Private OCI Registry on Kubernetes

Instead of using a third-party service, let's deploy the official Docker Registry (v2) into our cluster. We will secure it with basic authentication and back it with persistent storage.

Step 1: Generate Authentication Credentials

Use htpasswd locally to generate an encrypted password file.

Bash

htpasswd -Bc auth your-username

Create a Kubernetes Secret from this file so the registry pod can use it:

Bash

kubectl create secret generic registry-auth --from-file=htpasswd=auth

Step 2: Provision Persistent Storage (PVC)

Ensure your charts are not lost if the pod restarts. We are requesting 10GB using a local storage class (adjust storageClassName based on your cluster, such as local-path for RKE2).

YAML

# registry-pvc.yaml
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
  name: registry-pvc
spec:
  accessModes:
    - ReadWriteOnce
  storageClassName: local-path
  resources:
    requests:
      storage: 10Gi

Step 3: Deploy the Registry, Service, and Ingress

Here is the complete manifest to deploy the registry, attach the storage, enforce authentication, and expose it to the internet.

YAML

# registry.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
  name: private-registry-deployment
  labels:
    app: private-registry
spec:
  replicas: 1
  selector:
    matchLabels:
      app: private-registry
  template:
    metadata:
      labels:
        app: private-registry
    spec:
      containers:
      - name: registry
        image: registry:2
        ports:
        - containerPort: 5000
        env:
        - name: REGISTRY_STORAGE_DELETE_ENABLED
          value: "true"
        - name: REGISTRY_AUTH
          value: "htpasswd"
        - name: REGISTRY_AUTH_HTPASSWD_REALM
          value: "Registry Realm"
        - name: REGISTRY_AUTH_HTPASSWD_PATH
          value: "/auth/htpasswd"
        volumeMounts:
        - name: registry-storage
          mountPath: /var/lib/registry
        - name: auth-volume
          mountPath: /auth
          readOnly: true
      volumes:
      - name: registry-storage
        persistentVolumeClaim:
          claimName: registry-pvc
      - name: auth-volume
        secret:
          secretName: registry-auth

---
apiVersion: v1
kind: Service
metadata:
  name: private-registry-service
spec:
  selector:
    app: private-registry
  ports:
    - protocol: TCP
      port: 80
      targetPort: 5000

---
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
  name: private-registry-ingress
  annotations:
    cert-manager.io/cluster-issuer: "letsencrypt-prod"
    nginx.ingress.kubernetes.io/proxy-body-size: "0" 
spec:
  ingressClassName: nginx
  tls:
  - hosts:
    - registry.yourdomain.com
    secretName: registry-tls-secret
  rules:
  - host: registry.yourdomain.com
    http:
      paths:
      - path: /
        pathType: Prefix
        backend:
          service:
            name: private-registry-service
            port:
              number: 80

Apply the manifests:

Bash

kubectl apply -f registry-pvc.yaml
kubectl apply -f registry.yaml

4. Pushing Your Chart to the Registry

With your OCI registry live at registry.yourdomain.com, you can now package and push your chart exactly like a Docker image.

Step 1: Package the Chart

Bundle your chart directory into a single archive (it will output a file like my-app-0.1.0.tgz).

Bash

helm package ./my-app

Step 2: Log in to the Registry

Authenticate using the credentials you generated earlier.

Bash

helm registry login registry.yourdomain.com -u your-username

Step 3: Push the Chart

Push the packaged archive to the registry under a clean /charts path.

Bash

helm push my-app-0.1.0.tgz oci://registry.yourdomain.com/charts

5. Deploying from Your Private Registry

Your chart is now safely stored in your OCI registry. You, or your CI/CD pipelines, can install it directly onto any cluster without needing the local source files.

Bash

helm install my-release oci://registry.yourdomain.com/charts/my-app --version 0.1.0

To verify the deployment:

Bash

helm ls
kubectl get pods

When you are done, Helm makes cleanup effortless. A single command removes all resources tied to that release:

Bash

helm uninstall my-release

Conclusion

By treating Helm charts as standard OCI artifacts, you streamline your deployment pipelines and consolidate your infrastructure. You no longer need separate systems for container images and Kubernetes manifests—everything lives securely in one place.

Subscribe to Experiment Lab

Don’t miss out on the latest issues. Sign up now to get access to the library of members-only issues.
jamie@example.com
Subscribe