15 min to read
Creating and Hosting Helm Charts for Kubernetes Operators
Build, package, and distribute your Helm charts using GitHub Pages

Overview
Helm charts provide a powerful way to package, distribute, and deploy Kubernetes applications. This guide focuses on creating Helm charts specifically for Kubernetes Operators and hosting them on GitHub Pages, creating a free, reliable chart repository accessible to anyone.
This guide covers the complete workflow for creating and hosting Helm charts:
- Creating a Helm chart structure for Kubernetes Operators
- Properly packaging Helm charts for distribution
- Setting up GitHub Pages as a Helm chart repository
- Automating chart updates with GitHub Actions
- Best practices for chart maintenance and versioning
Helm Chart Creation
Installation
Before you begin, make sure you have Helm installed:
# macOS
brew install helm
# Linux
curl -fsSL -o get_helm.sh https://raw.githubusercontent.com/helm/helm/main/scripts/get-helm-3
chmod 700 get_helm.sh
./get_helm.sh
# Windows (using Chocolatey)
choco install kubernetes-helm
# Verify installation
helm version
Chart Structure for Operators
Creating a Helm chart for a Kubernetes Operator requires special consideration for CRDs and operator-specific resources.
You can create a new chart using the helm create
command, but for an operator, you’ll likely need to customize it:
# Create basic chart structure
helm create my-operator
# Clean up default templates we don't need
rm -rf my-operator/templates/*
Here’s a complete structure for a Kubernetes Operator chart, based on a real example (helios-lb):
helm/
├── README.md # Chart usage guide and parameter documentation
└── helios-lb/ # Main chart directory
├── Chart.yaml # Chart metadata (version, dependencies, etc.)
├── crds/ # CRD definition files
│ └── heliosconfig.yaml # HeliosConfig CRD specification
├── templates/ # Kubernetes resource templates
│ ├── NOTES.txt # Usage notes displayed after installation
│ ├── _helpers.tpl # Common template functions
│ ├── crd-cleanup-hook.yaml # Helm hook for CRD cleanup
│ ├── customresource/ # CR templates
│ │ └── config.yaml # Example CR instance
│ ├── deployment.yaml # Controller deployment
│ ├── namespace.yaml # Namespace definition
│ ├── rbac.yaml # Permission settings (Role, RoleBinding)
│ ├── service.yaml # Service definition
│ └── serviceaccount.yaml # ServiceAccount definition
├── values/ # Values files for different use cases
│ ├── basic-values.yaml # Basic configuration
│ ├── port-values.yaml # Port-based configuration
│ └── weight-values.yaml # Weight-based configuration
└── values.yaml # Default values file
Key Files Explained
Chart.yaml
This file contains metadata about your chart:
apiVersion: v2
name: helios-lb
description: A Helm chart for Helios Load Balancer Operator
type: application
version: 0.1.0 # Chart version
appVersion: "1.0.0" # Application version
maintainers:
- name: somaz
email: somaz@example.com
keywords:
- kubernetes
- operator
- loadbalancer
sources:
- https://github.com/somaz94/helios-lb
values.yaml
Default configuration values for your chart:
# Default values for helios-lb
# This is a YAML-formatted file.
# Operator configuration
operator:
image:
repository: somaz/helios-lb
tag: latest
pullPolicy: Always
resources:
limits:
cpu: 200m
memory: 256Mi
requests:
cpu: 100m
memory: 128Mi
nodeSelector: {}
tolerations: []
affinity: {}
# CRD configuration
crd:
install: true
cleanup: false
# Default CR configuration
defaultConfig:
enabled: false
name: "default-config"
type: "weight" # weight or port
services:
- name: service-a
weight: 80
- name: service-b
weight: 20
RBAC Configuration (rbac.yaml)
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRole
metadata:
name: {{ include "helios-lb.fullname" . }}-role
rules:
- apiGroups:
- ""
resources:
- services
- endpoints
verbs:
- get
- list
- watch
- apiGroups:
- apps
resources:
- deployments
verbs:
- get
- list
- watch
- create
- update
- patch
- delete
- apiGroups:
- lb.helios.com
resources:
- heliosconfigs
- heliosconfigs/status
verbs:
- get
- list
- watch
- create
- update
- patch
- delete
---
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRoleBinding
metadata:
name: {{ include "helios-lb.fullname" . }}-rolebinding
roleRef:
apiGroup: rbac.authorization.k8s.io
kind: ClusterRole
name: {{ include "helios-lb.fullname" . }}-role
subjects:
- kind: ServiceAccount
name: {{ include "helios-lb.serviceAccountName" . }}
namespace: {{ .Release.Namespace }}
CRD Definition Example
apiVersion: apiextensions.k8s.io/v1
kind: CustomResourceDefinition
metadata:
name: heliosconfigs.lb.helios.com
spec:
group: lb.helios.com
names:
kind: HeliosConfig
listKind: HeliosConfigList
plural: heliosconfigs
singular: heliosconfig
shortNames:
- hlb
scope: Namespaced
versions:
- name: v1
served: true
storage: true
schema:
openAPIV3Schema:
type: object
properties:
apiVersion:
type: string
kind:
type: string
metadata:
type: object
spec:
type: object
required: ["type", "services"]
properties:
type:
type: string
enum: ["weight", "port"]
services:
type: array
items:
type: object
required: ["name"]
properties:
name:
type: string
weight:
type: integer
minimum: 0
maximum: 100
port:
type: integer
minimum: 1
maximum: 65535
Packaging Helm Charts
Once your chart structure is complete, you can package it for distribution.
Package Your Chart
# Navigate to your chart's parent directory
cd path/to/helm/
# Package the chart
helm package ./helios-lb
# This creates a file like helios-lb-0.1.0.tgz
Make sure your Chart.yaml has the correct version information before packaging. Every time you make changes to your chart, you should update the version and repackage it.
Hosting on GitHub Pages
GitHub Pages provides a simple way to host your Helm chart repository.
Setup Process
Step | Description |
---|---|
Create Repository | Create a GitHub repository for your chart (if you haven't already) |
Create gh-pages Branch | Create a special branch to host your chart repository |
Add Packaged Charts | Add your .tgz chart packages to this branch |
Create Index | Create an index.yaml file to make it a valid Helm repository |
Push to GitHub | Push the branch to GitHub to activate GitHub Pages |
Configure GitHub Pages | Ensure GitHub Pages is configured to use the gh-pages branch |
Create a Clean gh-pages Branch
It’s important to create a clean gh-pages branch that contains only the repository files:
# Create and check out a new orphan branch (no history)
git checkout --orphan gh-pages
# Remove all tracked files
git rm -rf .
# Create a helm-repo directory for organization (optional)
mkdir helm-repo
cd helm-repo
# Copy your packaged chart here
cp ../../path/to/helm/helios-lb-0.1.0.tgz .
# Create the repository index
helm repo index . --url https://username.github.io/repo-name/helm-repo
# Go back to root
cd ..
# Add all files
git add .
# Commit changes
git commit -m "Add Helm chart repository"
# Push to GitHub
git push origin gh-pages
After pushing, GitHub will automatically set up GitHub Pages for your repository. You can check this in your repository settings under “Pages.”
You can organize your repository by creating subdirectories:
/helm-repo
- For chart packages and index/docs
- For additional documentation/examples
- For usage examples
Adding Multiple Chart Versions
When you update your chart, you’ll want to make new versions available while maintaining older versions:
# Update Chart.yaml with new version (e.g., 0.2.0)
# Package the new version
helm package ./path/to/helm/helios-lb
# Copy to your gh-pages repo
cp helios-lb-0.2.0.tgz path/to/gh-pages/helm-repo/
# Update the index to include both versions
cd path/to/gh-pages/helm-repo/
helm repo index . --url https://username.github.io/repo-name/helm-repo
# Commit and push
git add .
git commit -m "Add chart version 0.2.0"
git push origin gh-pages
Automating with GitHub Actions
You can automate chart publishing with GitHub Actions. Here’s a workflow example:
name: Release Charts
on:
push:
branches:
- main
paths:
- 'helm/**'
jobs:
release:
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v2
with:
fetch-depth: 0
- name: Configure Git
run: |
git config user.name "$GITHUB_ACTOR"
git config user.email "$GITHUB_ACTOR@users.noreply.github.com"
- name: Install Helm
uses: azure/setup-helm@v1
with:
version: v3.8.1
- name: Package Chart
run: |
mkdir -p .cr-release-packages
helm package helm/helios-lb -d .cr-release-packages
- name: Checkout gh-pages
uses: actions/checkout@v2
with:
ref: gh-pages
path: gh-pages
- name: Update Repository
run: |
mkdir -p gh-pages/helm-repo
cp .cr-release-packages/* gh-pages/helm-repo/
cd gh-pages/helm-repo
helm repo index . --url https://$.github.io/$/helm-repo
- name: Publish Repository
run: |
cd gh-pages
git add .
git commit -m "Release new chart version" || echo "No changes to commit"
git push origin gh-pages
This workflow automatically packages and publishes your chart when changes are made to the helm directory in the main branch.
Using Your Chart Repository
Once your repository is set up, others can use it by adding it to their Helm:
# Add the repository
helm repo add helios-lb https://username.github.io/repo-name/helm-repo
# Update repositories
helm repo update
# Search for available charts
helm search repo helios-lb
# Install the chart
helm install my-release helios-lb/helios-lb
# Install with custom values
helm install my-release helios-lb/helios-lb --values custom-values.yaml
Best Practices for Helm Charts
- Keep your gh-pages branch clean, containing only repository files
- Use semantic versioning (MAJOR.MINOR.PATCH) for your charts
- Include comprehensive documentation in README.md
- Document all values with descriptions and defaults
- Add NOTES.txt with post-installation instructions
- Include examples for common use cases
- Test charts thoroughly before publishing
- Maintain backward compatibility when possible
- Update appVersion and version in Chart.yaml appropriately
- Include a CHANGELOG.md to track changes
- Set appropriate dependencies and version constraints
- Place CRDs in the crds/ directory (not templates/)
- Consider hooks for careful CRD lifecycle management
- Add safeguards to prevent accidental CRD deletion
- Make CRD installation optional with values
CRD Handling Special Considerations
CRDs require special handling in Helm charts:
-
Placement: CRDs should be placed in the
crds/
directory, not intemplates/
. This ensures they are installed before any resources that depend on them. -
Upgrade Challenges: Helm doesn’t automatically upgrade CRDs to prevent data loss. Consider using a pre-upgrade hook.
-
Deletion Protection: Add a cleanup hook to control CRD deletion:
# templates/crd-cleanup-hook.yaml
{{- if .Values.crd.cleanup }}
apiVersion: batch/v1
kind: Job
metadata:
name: {{ include "helios-lb.fullname" . }}-crd-cleanup
annotations:
"helm.sh/hook": pre-delete
"helm.sh/hook-weight": "0"
"helm.sh/hook-delete-policy": hook-succeeded
spec:
template:
spec:
serviceAccountName: {{ include "helios-lb.serviceAccountName" . }}
containers:
- name: kubectl
image: bitnami/kubectl:latest
command:
- kubectl
- delete
- crd
- heliosconfigs.lb.helios.com
restartPolicy: Never
backoffLimit: 1
{{- end }}
Complete Example: Helios Load Balancer
Let’s look at a complete example for using our Helios Load Balancer operator chart:
# Add repository
helm repo add helios-lb https://somaz94.github.io/helios-lb/helm-repo
# Update helm repositories
helm repo update
# Check available chart versions
helm search repo helios-lb -l
# Install with default values
helm install my-lb helios-lb/helios-lb
# Install with custom configuration
helm install my-lb helios-lb/helios-lb \
--set operator.resources.limits.memory=512Mi \
--set defaultConfig.enabled=true \
--set defaultConfig.type=weight
# Install using a specific values file
helm install my-lb helios-lb/helios-lb -f values/weight-values.yaml
# Upgrade existing installation
helm upgrade my-lb helios-lb/helios-lb
# Verify installation
kubectl get pods
kubectl get heliosconfigs
# Uninstall
helm uninstall my-lb
Example Custom Values File
# weight-config.yaml
operator:
resources:
limits:
cpu: 300m
memory: 512Mi
defaultConfig:
enabled: true
name: "production-lb"
type: "weight"
services:
- name: service-v1
weight: 90
- name: service-v2
weight: 10
Comments