Helm is a package manager for Kubernetes. Helm uses a package format called Charts. A Chart is a collection of files that describes a coherent set of Kubernetes resources. Just as apt simplifies installing and managing software on Debian/Ubuntu, Helm does the same for Kubernetes resources. In this guide, we’ll show you how to create and use your own Helm Chart.
- Before you begin, install the Helm package manager and kubectl.
- For this guide, we assume you’re somewhat familiar with Kubernetes, such as ‘creating Kubernetes objects’, labels and selectors, and using existing Helm Charts.
Creating a Helm Chart manually
Step 1
A Helm Chart uses a specific folder structure and set of files. You can create these manually, or automatically with “helm create”. The latter is the quickest and simplest, and works as follows:
helm create demo-ChartThis command automatically creates a directory with the following files:
demo-Chart
├── Chart.yaml
├── values.yaml
├── Charts/
└── templates/
├── _helpers.tpl
├── NOTES.txt
├── deployment.yaml
├── service.yaml
├── ingress.yaml
├── hpa.yaml
├── serviceaccount.yaml
└── tests/
└── test-connection.yaml
Step 2
From here, edit the files as needed. Start by opening “Chart.yaml”, for example:
nano demo-Chart/Chart.yamlAdjust the contents as desired and save your changes (ctrl + x > y > enter), for example:
apiVersion: v2
name: demo-Chart
description: A Helm Chart for Nginx
type: application
version: 0.1.0
appVersion: "1.29.1"
kubeVersion: ">=1.24.0-0"
maintainers:
- name: Your Name
email: you@example.comThis file contains metadata for your Chart, such as its name, version, and description. The required fields in the Chart.yaml file are apiVersion, name, and version, which define the Chart API version, the Chart name, and the Chart version respectively. The remaining fields are optional and can be used to provide additional information about the Chart.
You can find the correct appVersion by checking, for example, the current software version in an existing Chart on Artifact Hub.
The most commonly used fields in Chart.yaml are:
apiVersion: v2 # The Chart API version, always "v2" (required)
name: demo-Chart # The name of the Chart (required)
description: "Short description" # A description of your project
type: application # Application or Library; see the default comments in Chart.yaml
version: 0.1.0 # The Chart version (SemVer, required)
appVersion: "8.0" # App version (free-form)
kubeVersion: ">=1.24.0-0" # Compatible Kubernetes version
keywords: "Software A, B" # A list of project keywords (optional)
home: https://... # Project homepage
sources: # URLs pointing to source code
- https://...
maintainers: # Optional maintainer details for the Helm Chart
- name: ...
email: ...
url: ...
engine: gotpl # Template engine name (default is gotpl)
icon: https://.../icon.png # URL to an SVG or PNG icon
deprecated: 0 # Whether this Chart is deprecated (boolean)
Step 3
The next file, values.yaml, is one of the most important: this is where you define the values of many variables. In any file where a variable appears, its value is automatically filled in by referencing it from values.yaml, for example: {{ .Values.replicaCount }}. Here, .Values indicates the variable resides in values.yaml, and replicaCount is the variable being used.
The benefit of using values.yaml is that you don’t have to edit other .yaml files when you change variable values—you do it from this single file: values.yaml. First open values.yaml:
nano demo-Chart/values.yamlAdjust the file as you like—for a minimal Nginx deployment, for instance—then save your changes (without the comments) (ctrl + x > y > enter). Here we assume an Nginx setup with a corresponding service.
deploymentName: nginx
# A short, recognisable name also used in labels/selectors and resource names.
replicaCount: 1
# Number of pods. For stateful DBs usually 1 (unless you arrange replication/clustering).
image:
repository: nginx
# The container image name. Three options:
# - Name as on Docker Hub (e.g. the mysql in https://hub.docker.com/_/mysql): "mysql"
# - A full docker.io address: "docker.io/library/mysql"
# - A private registry, for example: "registry.example.com/team/mysql"
tag: "1.29.1"
# Image version. For production, a fixed tag is recommended, e.g. 1.29.1.
# Alternatively, you can use "latest".
pullPolicy: IfNotPresent
# - Always: pulls the image every time the pod restarts.
# - IfNotPresent: only pulls if the image isn’t present locally (default).
# - Never: never pull (image MUST exist locally; rarely needed).
service:
name: nginx
# Kubernetes Service name (cluster-internal DNS).
type: ClusterIP
# Choose one of the following Service types:
# - ClusterIP: internal-only (within the cluster). Safest option for databases.
# - NodePort: external via each node:port; simple but limited.
# - LoadBalancer: external IP via cloud LB; use only if the service must be publicly reachable.
port: 80 # Port the service listens on.
protocol: TCP # TCP, UDP
Step 4
The ‘templates’ folder contains template files for the Kubernetes resources managed by your Chart. Open deployment.yaml, for example:
nano demo-Chart/templates/deployment.yamlThe default file includes many options; see also the earlier guide creating Kubernetes objects.
For a simple Nginx deployment, a layout like the one below is sufficient. You’ll see various values referenced with a structure such as {{ .Values.deploymentName }}. These are filled in automatically based on the deploymentName in values.yaml (see step 3).
apiVersion: apps/v1
kind: Deployment
metadata:
name: {{ .Values.deploymentName }}
labels:
app.kubernetes.io/name: {{ .Values.deploymentName }}
spec:
replicas: {{ .Values.replicaCount }}
selector:
matchLabels:
app.kubernetes.io/name: {{ .Values.deploymentName }}
template:
metadata:
labels:
app.kubernetes.io/name: {{ .Values.deploymentName }}
spec:
containers:
- name: nginx
image: "{{ .Values.image.repository }}:{{ .Values.image.tag }}"
imagePullPolicy: {{ .Values.image.pullPolicy }}
ports:
- name: http
containerPort: {{ .Values.service.port }}
Step 5
In step 3 we also included values for an Nginx service. There’s already an example for services in demo-Chart/templates/services.yaml:
nano demo-Chart/templates/services.yamlThanks to the configuration in values.yaml, you can reference those values in services.yaml too—for a simple Nginx setup, for example:
apiVersion: v1
kind: Service
metadata:
name: {{ .Values.service.name }}
labels:
app.kubernetes.io/name: {{ .Values.deploymentName }}
spec:
type: {{ .Values.service.type }}
selector:
app.kubernetes.io/name: {{ .Values.deploymentName }}
ports:
- port: {{ .Values.service.port }}
targetPort: {{ .Values.service.port }}
protocol: {{ .Values.service.protocol }}
Step 6 – optional
So far we’ve assumed a minimal setup. If you’d also like to add an ingress, you can do so by adding the required variables to values.yaml:
nano demo-Chart/values.yamlEdit the file as desired (without the comments) and save your changes (ctrl + x > y > enter).
ingress:
enabled: true # enable the ingress resource
ingressClassName: nginx # omit this if you only use annotations, e.g. below.
annotations: # optional; remove this if you use ingressClassName
nginx.ingress.kubernetes.io/ssl-redirect: "false"
nginx.ingress.kubernetes.io/proxy-body-size: "10m"
hosts:
- host: example.local # the hostname of your Nginx setup
paths:
- path: /
pathType: Prefix
# optionally override serviceName and servicePort (from the service above)
# serviceName: nginx-demo
# servicePort: 80
# Optional with TLS; this requires a Secret.
tls:
- hosts:
- example.local
SecretName: example-local-tlsThere are several options for using Secrets; see for example this Nginx Chart, or our Traefik guide for an example of creating and using a Secret in a Kubernetes object.
The default demo-Chart/templates/ingress.yaml file is cleverly put together with if statements so it won’t be used unless you enable ingress in values.yaml and provide the relevant variables; otherwise everything is filled in automatically. For completeness, here’s the default content of ingress.yaml.
{{- if .Values.ingress.enabled -}}
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: {{ include "demo-Chart.fullname" . }}
labels:
{{- include "demo-Chart.labels" . | nindent 4 }}
{{- with .Values.ingress.annotations }}
annotations:
{{- toYaml . | nindent 4 }}
{{- end }}
spec:
{{- with .Values.ingress.className }}
ingressClassName: {{ . }}
{{- end }}
{{- if .Values.ingress.tls }}
tls:
{{- range .Values.ingress.tls }}
- hosts:
{{- range .hosts }}
- {{ . | quote }}
{{- end }}
SecretName: {{ .SecretName }}
{{- end }}
{{- end }}
rules:
{{- range .Values.ingress.hosts }}
- host: {{ .host | quote }}
http:
paths:
{{- range .paths }}
- path: {{ .path }}
{{- with .pathType }}
pathType: {{ . }}
{{- end }}
backend:
service:
name: {{ include "demo-Chart.fullname" $ }}
port:
number: {{ $.Values.service.port }}
{{- end }}
{{- end }}
{{- end }}
Step 7
There are a few more files in the templates folder, such as hpa.yaml (not yet supported on our platform) and serviceaccount.yaml. These are set up so they won’t be used unless you specify otherwise in values.yaml. You’re free to delete NOTES.txt, hpa.yaml, and serviceaccount.yaml. Do keep _helpers.tpl: it contains small snippets you can call from other files, such as {{ include "demo-Chart.name" . }}.
No further changes are needed, and you can proceed to install your Helm Chart.
Installing and managing the Helm Chart
Installing the Helm Chart
There are several ways to install a Helm Chart (i.e. your Helm release). We’ll start with the simplest and assume you’re on the command line (any OS) in the directory where the Helm Chart was created (/demo-Chart/ in this example). You can find more information about the helm install command here.
helm install nginx -f values.yamlOptionally add -n namespace to the command and change ‘namespace’ to the desired name. This determines which namespace the Helm Chart is installed into. This is optional and requires an existing namespace (kubectl create ns namespacename).
In our example we didn’t assume a setup with sensitive passwords. If you do need those, there are a few ways to provide them (the examples below assume a MySQL setup):
Using a Secret (for production environments):
helm install mysql-demo \
--set mysql.existingSecret=mysql-prod-Secret \
--set mysql.database=mydbCreating Secrets is beyond the scope of this guide, but you’ll find an example of creating and using Secrets in our Traefik guide.
Inline passwords (for test environments):
helm install mysql-demo \
--set mysql.rootPassword="$(openssl rand -base64 24)" \
--set mysql.password="$(openssl rand -base64 24)" \
--set mysql.user=appuser
Checking your Helm release
You can list all releases on your cluster with:
helm listThe output looks like this (note the scrollbar):
NAME NAMESPACE REVISION UPDATED STATUS Chart APP VERSION
mysql-1670416172 default 1 2022-12-07 13:29:33.9111825 +0100 STD deployed mysql-9.4.4 8.0.31For more specific information (similar to the output of the ‘helm install’ command), run:
helm status releasenameOf course, you can also check individual components of your release with familiar kubectl commands, for example:
kubectl get deploymentIf you’re using a namespace (recommended), don’t forget to add -n namespacename to the command.
Removing your Helm release
Removing a release is as straightforward as installing it:
helm uninstall releasenameReplace ‘releasename’ with the name of the release as shown under ‘NAME’ in the output of the ‘helm list’ command.
Optionally, you can add the --keep-history flag. This is a handy way to retain information about the release:
helm uninstall releasename --keep-historyYou can then review the history of the ‘releasename’ later with:
helm history releasenameHelm goes a step further: using this information, you can roll back a release after uninstalling it with:
helm rollback releasename revisionYou’ll find the revision number in the output of the helm history command.
That brings us to the end of this guide on creating Helm Charts. In a future update, we’ll expand it with additional components such as secrets, persistent volumes, etc.