This guide walks you through creating a new Helm chart and developing it according to the repository’s standards.
Creating a New Chart
Generate the chart scaffold
Use Helm’s built-in command to create a basic chart structure:cd charts/
helm create my-new-chart
This creates a directory with the standard chart structure. Review the generated structure
The generated chart includes:my-new-chart/
├── Chart.yaml # Chart metadata
├── values.yaml # Default configuration values
├── templates/ # Kubernetes manifest templates
│ ├── deployment.yaml
│ ├── service.yaml
│ ├── ingress.yaml
│ ├── serviceaccount.yaml
│ ├── _helpers.tpl # Template helper functions
│ ├── NOTES.txt # Post-install notes
│ └── tests/ # Helm tests
└── .helmignore # Files to ignore when packaging
Customize Chart.yaml
Update the chart metadata with accurate information:apiVersion: v2
name: my-new-chart
description: A Helm chart for deploying My Application
home: https://myapp.example.com
type: application
version: 0.1.0
appVersion: "1.0.0"
maintainers:
- name: douban
Create templates
Develop your Kubernetes manifest templates following best practices.
Chart Structure Deep Dive
Chart.yaml
The Chart.yaml file contains metadata about your chart:
apiVersion: v2 # Helm 3 uses apiVersion v2
name: nginx # Chart name (must match directory name)
description: A Helm chart for Kubernetes
home: https://nginx.org/ # Project homepage
type: application # Type: 'application' or 'library'
version: 0.4.1 # Chart version (SemVer)
appVersion: "1.16.0" # Version of the application
maintainers:
- name: douban
Important fields:
version: Must be incremented with every chart change
appVersion: The version of the application being deployed
type: Use application for deployable charts, library for shared templates
values.yaml
The values.yaml file defines configurable parameters with sensible defaults:
replicaCount: 1
image:
repository: nginx
pullPolicy: IfNotPresent
tag: "" # Defaults to appVersion if empty
service:
type: ClusterIP
port: 80
resources: {}
# limits:
# cpu: 100m
# memory: 128Mi
# requests:
# cpu: 100m
# memory: 128Mi
Leave resource limits empty by default to allow charts to run in resource-constrained environments. Users should consciously set these based on their needs.
templates/ Directory
This directory contains Go template files that generate Kubernetes manifests.
Common Templates
Most charts in this repository include:
- deployment.yaml: The main Deployment resource
- service.yaml: Service to expose the application
- ingress.yaml: Optional Ingress for external access
- serviceaccount.yaml: ServiceAccount for the pods
- servicemonitor.yaml: Optional ServiceMonitor for Prometheus
- prometheusrule.yaml: Optional PrometheusRule for alerting
- httproute.yaml: Optional HTTPRoute for Gateway API
_helpers.tpl
This file contains reusable template functions. Every chart includes these standard helpers:
{{/*
Expand the name of the chart.
*/}}
{{- define "nginx.name" -}}
{{- default .Chart.Name .Values.nameOverride | trunc 63 | trimSuffix "-" }}
{{- end }}
{{/*
Create a default fully qualified app name.
*/}}
{{- define "nginx.fullname" -}}
{{- if .Values.fullnameOverride }}
{{- .Values.fullnameOverride | trunc 63 | trimSuffix "-" }}
{{- else }}
{{- $name := default .Chart.Name .Values.nameOverride }}
{{- if contains $name .Release.Name }}
{{- .Release.Name | trunc 63 | trimSuffix "-" }}
{{- else }}
{{- printf "%s-%s" .Release.Name $name | trunc 63 | trimSuffix "-" }}
{{- end }}
{{- end }}
{{- end }}
{{/*
Create chart name and version as used by the chart label.
*/}}
{{- define "nginx.chart" -}}
{{- printf "%s-%s" .Chart.Name .Chart.Version | replace "+" "_" | trunc 63 | trimSuffix "-" }}
{{- end }}
{{/*
Common labels
*/}}
{{- define "nginx.labels" -}}
helm.sh/chart: {{ include "nginx.chart" . }}
{{ include "nginx.selectorLabels" . }}
{{- if .Chart.AppVersion }}
app.kubernetes.io/version: {{ .Chart.AppVersion | quote }}
{{- end }}
app.kubernetes.io/managed-by: {{ .Release.Service }}
{{- end }}
{{/*
Selector labels
*/}}
{{- define "nginx.selectorLabels" -}}
app.kubernetes.io/name: {{ include "nginx.name" . }}
app.kubernetes.io/instance: {{ .Release.Name }}
{{- end }}
{{/*
Create the name of the service account to use
*/}}
{{- define "nginx.serviceAccountName" -}}
{{- if .Values.serviceAccount.create }}
{{- default (include "nginx.fullname" .) .Values.serviceAccount.name }}
{{- else }}
{{- default "default" .Values.serviceAccount.name }}
{{- end }}
{{- end }}
Replace nginx with your chart name in all template definitions.
Testing with chart-testing
The repository uses chart-testing (ct) for validation.
Setting Up chart-testing
Install dependencies
# Install Helm 3
curl https://raw.githubusercontent.com/helm/helm/master/scripts/get-helm-3 | bash
# Install chart-testing
pip install yamllint yamale
# Or use the ct Docker image
Add dependency repositories
If your chart depends on external charts, add the repositories:helm repo add stable https://charts.helm.sh/stable
helm repo add bitnami https://charts.bitnami.com/bitnami
helm repo update
Run linting
Test your chart against linting rules:ct lint --chart-dirs charts --charts charts/my-new-chart
What ct lint Checks
The linting process validates:
-
Chart.yaml validation
- Required fields are present
- Version follows semantic versioning
- Version was incremented (for changed charts)
-
values.yaml formatting
- Valid YAML syntax
- Proper indentation
- No duplicate keys
-
Template validation
- Templates can be rendered
- Generated manifests are valid Kubernetes resources
- No deprecated API versions
-
Maintainer guidelines
- README exists and is not empty
- NOTES.txt provides helpful information
Configuration (ct.yaml)
The repository’s ct.yaml configures chart-testing:
remote: origin
chart-dirs:
- charts # Directory containing charts
chart-repos: # External chart repositories
- stable=https://charts.helm.sh/stable/
- douban=https://douban.github.io/charts/
- bitnami=https://raw.githubusercontent.com/bitnami/charts/archive-full-index/bitnami
- timescale=https://charts.timescale.com/
helm-extra-args: --timeout 600s # Extra args for helm commands
Linting Requirements
Beyond chart-testing, follow these best practices:
- Use 2-space indentation
- Use
# comments for documentation
- Quote strings when necessary
- Keep lines under 120 characters when possible
Template Best Practices
-
Always set resource names using helpers
metadata:
name: {{ include "nginx.fullname" . }}
labels:
{{- include "nginx.labels" . | nindent 4 }}
-
Use conditional rendering
{{- if .Values.ingress.enabled }}
apiVersion: networking.k8s.io/v1
kind: Ingress
# ...
{{- end }}
-
Quote string values
image: "{{ .Values.image.repository }}:{{ .Values.image.tag | default .Chart.AppVersion }}"
-
Use
.Values for all configurable parameters
replicas: {{ .Values.replicaCount }}
-
Provide sensible defaults
image:
tag: "{{ .Values.image.tag | default .Chart.AppVersion }}"
Documentation Requirements
- README.md: Describe what the chart does, how to install it, and document all parameters
- NOTES.txt: Provide post-installation instructions
- values.yaml comments: Comment all configurable values
Release Process
When your chart is ready and merged to master, it’s automatically released.
Automated Release Workflow
The release.yaml workflow handles releases:
name: Release Charts
on:
push:
branches:
- master
jobs:
release:
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v2
- name: Fetch history
run: git fetch --prune --unshallow
- name: Configure Git
run: |
git config user.name "$GITHUB_ACTOR"
git config user.email "[email protected]"
- name: Install Helm
run: |
curl -fsSLo get_helm.sh https://raw.githubusercontent.com/helm/helm/master/scripts/get-helm-3
chmod 700 get_helm.sh
./get_helm.sh
- name: Add dependency chart repos
run: |
helm repo add stable https://charts.helm.sh/stable
helm repo add douban https://douban.github.io/charts/
helm repo add bitnami https://charts.bitnami.com/bitnami
- name: Run chart-releaser
uses: helm/[email protected]
env:
CR_TOKEN: "${{ secrets.GITHUB_TOKEN }}"
What Happens During Release
Detect changed charts
chart-releaser detects which charts have changed since the last release.
Package charts
Changed charts are packaged into .tgz files.
Create GitHub Release
A GitHub Release is created for each chart with the version from Chart.yaml.
Upload packages
Chart packages are uploaded as release assets.
Update index
The index.yaml file is updated in the gh-pages branch.
After Release
Once released, users can install your chart:
helm repo add douban https://douban.github.io/charts
helm repo update
helm install my-release douban/my-new-chart
Common Patterns
Environment Variables
Support custom environment variables:
# values.yaml
env: []
# deployment.yaml
{{- with .Values.env }}
env:
{{- toYaml . | nindent 12 }}
{{- end}}
Volumes and Volume Mounts
# values.yaml
volumes: []
volumeMounts: []
# deployment.yaml
{{- with .Values.volumeMounts }}
volumeMounts:
{{- toYaml . | nindent 12 }}
{{- end }}
{{- with .Values.volumes}}
volumes:
{{- toYaml . | nindent 8 }}
{{- end }}
ServiceMonitor for Prometheus
# values.yaml
serviceMonitor:
enabled: false
# templates/servicemonitor.yaml
{{- if .Values.serviceMonitor.enabled }}
apiVersion: monitoring.coreos.com/v1
kind: ServiceMonitor
metadata:
name: {{ include "nginx.fullname" . }}
labels:
{{- include "nginx.labels" . | nindent 4 }}
spec:
selector:
matchLabels:
{{- include "nginx.selectorLabels" . | nindent 6 }}
endpoints:
- port: http
{{- end }}
Testing Your Chart
Before submitting:
# Lint the chart
ct lint --chart-dirs charts --charts charts/my-new-chart
# Install and test
helm install test-release charts/my-new-chart
helm test test-release
helm uninstall test-release
# Test with custom values
helm install test-release charts/my-new-chart -f my-values.yaml
Next Steps