Collect considered best practices for creating charts. This guide focuses primarily on best practices for charts that may be publicly deployed. Many charts are for internal-use only, and authors of such charts may find that their internal interests override suggestions here.
An example of a formatted Helm chart foo
created with helm create foo
can be found in folder docs/examples/foo
.
- Chart names must be lower case letters and numbers. Words may be separated with dashes (
-
). Neither uppercase letters nor underscores can be used in chart names. Dots should not be used in chart names. - Helm uses SemVer 2 to represent version numbers.
- YAML files should be indented using two spaces (and never tabs).
- Using the words Helm and helm:
- Helm refers to the project as a whole.
helm
refers to the client-side command.- The term
chart
does not need to be capitalized, as it is not a proper noun. - However, Chart.yaml does need to be capitalized because the file name is case sensitive.
- When in doubt, use Helm (with an uppercase 'H').
- The chart directory follows these conventions:
-
The directory name is the name of the chart (without versioning information).
-
Helm will expect a structure that matches this:
chart-name/ Chart.yaml # A YAML file containing information about the chart LICENSE # OPTIONAL: A plain text file containing the license for the chart README.md # OPTIONAL: A human-readable README file values.yaml # The default configuration values for this chart values.schema.json # OPTIONAL: A JSON Schema for imposing a structure on the values.yaml file charts/ # A directory containing any charts upon which this chart depends. crds/ # Custom Resource Definitions templates/ # A directory of templates that, when combined with values, # will generate valid Kubernetes manifest files. templates/NOTES.txt # OPTIONAL: A plain text file containing short usage notes
-
Directories
charts/
,crds/
, andtemplates/
are reserved.
-
-
The
Chart.yaml
file is required for a chart. It contains the following fields:apiVersion: The chart API version (required) name: The name of the chart (required) version: A SemVer 2 version (required) kubeVersion: A SemVer range of compatible Kubernetes versions (optional) description: A single-sentence description of this project (optional) type: The type of the chart (optional) keywords: - A list of keywords about this project (optional) home: The URL of this projects home page (optional) sources: - A list of URLs to source code for this project (optional) dependencies: # A list of the chart requirements (optional) - name: The name of the chart (nginx) version: The version of the chart ("1.2.3") repository: (optional) The repository URL ("https://example.com/charts") or alias ("@repo-name") condition: (optional) A yaml path that resolves to a boolean, used for enabling/disabling charts (e.g. subchart1.enabled ) tags: # (optional) - Tags can be used to group charts for enabling/disabling together import-values: # (optional) - ImportValues holds the mapping of source values to parent key to be imported. Each item can be a string or pair of child/parent sublist items. alias: (optional) Alias to be used for the chart. Useful when you have to add the same chart multiple times maintainers: # (optional) - name: The maintainers name (required for each maintainer) email: The maintainers email (optional for each maintainer) url: A URL for the maintainer (optional for each maintainer) icon: A URL to an SVG or PNG image to be used as an icon (optional). appVersion: The version of the app that this contains (optional). Needn't be SemVer. Quotes recommended. deprecated: Whether this chart is deprecated (optional, boolean) annotations: example: A list of annotations keyed by name (optional).
- The apiVersion field should be
v2
for Helm charts that require at least Helm 3. Charts supporting previous Helm versions have an apiVersion set tov1
and are still installable by Helm 3. - The
appVersion
field is informational and optional, and has no impact on chart version calculations. But it is recommended. - The
type
field defines the type of chart. There are two types: application and library.- Application is the default type and it is the standard chart which can be operated on fully.
- Library charts provides utilities or functions for the chart builder. A library chart differs from an application chart because it is not installable and usually doesn't contain any resource objects.
- The apiVersion field should be
Focus on designing a chart's values.yaml file.
-
Variable names should begin with a lowercase letter, and words should be separated with camelcase.
- Helm's built-in variables begin with an uppercase letter to easily distinguish them from user-defined values:
.Release.Name
,.Capabilities.KubeVersion
.
- Helm's built-in variables begin with an uppercase letter to easily distinguish them from user-defined values:
-
Flat values should be favored over nested. The reason for this is that it is simpler for template developers and users. Flat:
serverName: nginx serverPort: 80
Nested:
server: name: nginx port: 80
- However, when there are a large number of related variables, and at least one of them is non-optional, nested values may be used to improve readability.
-
To avoid type conversion errors is to be explicit about strings, and implicit about everything else. Or, in short, quote all strings.
- Large integers like
foo: 12345678
will get converted to scientific notation in some cases. To avoid the integer casting issues, it is advantageous to store your integers as strings as well, and use{{ int $value }}
in the template to convert from a string back to an integer.
- Large integers like
-
It's often better to structure your values file using maps in order to make it easy to override from
--set
. -
Every defined property in values.yaml should be documented. The documentation string should begin with the name of the property that it describes, and then give at least a one-sentence description.
- TODO: Evaluate
helm-docs
and its automation.
- TODO: Evaluate
Helm chart templates are basically Go templates. A developer's guide for chart templates can be found here.
-
The
templates/
directory should be structured as follows:-
Template files should have the extension
.yaml
if they produce YAML output. The extension.tpl
may be used for template files that produce no formatted content. -
Template file names should use dashed notation (
my-example-configmap.yaml
), not camelcase. -
Each resource definition should be in its own template file.
-
Template file names should reflect the resource kind in the name. e.g.
foo-po.yaml
,bar-svc.yaml
using abbreviations of Kubernetes Resource Types (if they exist):Abbreviation Full name svc service deploy deployment cm configmap secret secret ds daemonset rc replicationcontroller petset petset po pod hpa horizontalpodautoscaler ing ingress job job limit limitrange ns namespace pv persistentvolume pvc persistentvolumeclaim sa serviceaccount -
If applicable, prefix the files with the name of the Chart component (i.e an elasticsearch cluster has master, client and data components). For example:
master-deploy.yaml # deployment of a master component web-deploy.yaml # deployment of a web component web-svc.yaml # service definition of a web component registry-rc.yaml # replication controller of a registry component
-
-
All defined template (templates created inside a
{{ define }}
directive) names should be namespaced because they are globally accessible. Correct:{{- define "nginx.fullname" }} {{/* ... */}} {{ end -}}
Incorrect:
{{- define "fullname" -}} {{/* ... */}} {{ end -}}
-
Templates should be indented using two spaces (never tabs).
-
Template directives should have whitespace after the opening braces and before the closing braces: Correct:
{{ .foo }} {{ print "foo" }} {{- print "bar" -}}
Incorrect:
{{.foo}} {{print "foo"}} {{-print "bar"-}}
-
Chomp whitespace where possible:
foo: {{- range .Values.items }} {{ . }} {{ end -}}
-
Blocks (such as control structures) may be indented to indicate flow of the template code.
{{ if $foo -}} {{- with .Bar }}Hello{{ end -}} {{- end -}}
⚠️ Since YAML is a whitespace-oriented language, it is often not possible for code indentation to follow that convention. -
Keep the amount of whitespace in generated templates to a minimum. In particular, numerous blank lines should not appear adjacent to each other. But occasional empty lines (particularly between logical sections) is fine. Best:
apiVersion: batch/v1 kind: Job metadata: name: example labels: first: first second: second
Okay:
apiVersion: batch/v1 kind: Job metadata: name: example labels: first: first second: second
Avoid:
apiVersion: batch/v1 kind: Job metadata: name: example labels: first: first second: second
-
Comments. YAML comments:
# This is a comment type: sprocket
Template Comments:
{{- /* This is a comment. */}} type: frobnitz
-
YAML is a superset of JSON. In some cases, using a JSON syntax can be more readable than other YAML representations. Using JSON for increased legibility is good. However, JSON syntax should not be used for representing more complex constructs. When dealing with pure JSON embedded inside of YAML (such as init container configuration), it is of course appropriate to use the JSON format.
-
Use version ranges instead of pinning to an exact version. The suggested default is to use a patch-level version match:
version: ~1.2.3
The following provides a pre-release as well as patch-level matching:
version: ~1.2.3-0
-
Where possible, use
oci://
orhttps://
repository URLs, followed byhttp://
URLs. -
Conditions or tags should be added to any dependencies that are optional. The preferred form of a condition is:
condition: somechart.enabled
When multiple subcharts (dependencies) together provide an optional or swappable feature, those charts should share the same tags. For example, if both nginx and memcached together provide performance optimizations for the main app in the chart, and are required to both be present when that feature is enabled, then they should both have a tags section like this:
tags: - webaccelerator
Please note, GKE cluster labels and Kubernetes labels are not the same. GKE cluster labels are arbitrary metadata attached to the cloud resources (GKE cluster, nodes, GCP disks) used to track usage and billing information. Kubernetes labels are key/value pairs that are attached to Kubernetes objects (e.g. pods). Kubernetes uses labels internally to associate cluster components and resources (at Kubernetes level, not cloud resources) with one another and manage resource lifecycles. For this reason, GKE cluster labels (cloud resources) are not propagated to Kubernetes objects (workloads). These best practices only affect Kubernetes labels that are applied through Helm.
- An item of metadata should be a label under the following conditions:
- It is used by Kubernetes to identify this resource
- It is useful to expose to operators for the purpose of querying the system.
- If an item of metadata is not used for querying, it should be set as an annotation instead.
- Helm hooks are always annotations.
- The following table defines common labels that Helm charts use. Helm itself never requires that a particular label be present. REC are recommended, and should be placed onto a chart for global consistency. OPT are optional More information on the Kubernetes labels, prefixed with
app.kubernetes.io
, is available in the Kubernetes documentation.Name Status Description app.kubernetes.io/name
REC This should be the app name, reflecting the entire app. Usually {{ template "name" . }}
is used for this. This is used by many Kubernetes manifests, and is not Helm-specific.helm.sh/chart
REC This should be the chart name and version: `{{ .Chart.Name }}-{{ .Chart.Version app.kubernetes.io/managed-by
REC This should always be set to {{ .Release.Service }}
. It is for finding all things managed by Helm.app.kubernetes.io/instance
REC This should be the {{ .Release.Name }}
. It aids in differentiating between different instances of the same application.app.kubernetes.io/version
OPT The version of the app and can be set to {{ .Chart.AppVersion }}
.app.kubernetes.io/component
OPT This is a common label for marking the different roles that pieces may play in an application. For example, app.kubernetes.io/component: frontend
.app.kubernetes.io/part-of
OPT When multiple charts or pieces of software are used together to make one application. For example, application software and a database to produce a website. This can be set to the top level application being supported.
-
A container image should use a fixed tag or the SHA of the image. It should not use the tags latest, head, canary, or other tags that are designed to be "floating". Images may be defined in the values.yaml file to make it easy to swap out images.
image: {{ .Values.redisImage | quote }}
An image and a tag may be defined in values.yaml as two separate fields:
image: "{{ .Values.redisImage }}:{{ .Values.redisTag }}"
-
Set the
imagePullPolicy
toIfNotPresent
by default. -
All
PodTemplate
sections should specify a selector. -
Include Health Checks (probes, Liveness, Readiness and/or Startup Probes) wherever practical.
-
Allow configurable resource requests and limits.
-
Allow customization of the application configuration.
-
Always define named ports in the podSpec. Whenever possible name the ports with the IANA defined service name.
When working with Custom Resource Definitions (CRDs), it is important to distinguish two different pieces:
- There is a declaration of a CRD. This is the YAML file that has the kind
CustomResourceDefinition
. - Then there are resources that use the CRD. Say a CRD defines
foo.example.com/v1
. Any resource that hasapiVersion: example.com/v1
andkind Foo
is a resource that uses the CRD.
-
The declaration must be registered before any resources of that CRDs kind(s) can be used. And the registration process sometimes takes a few seconds. There are 2 methods:
- Method 1: Let helm Do It For You: Put CRDs in a special directory called
crds
. These CRDs are not templated, but will be installed by default when running ahelm install
for the chart. If the CRD already exists, it will be skipped with a warning. If you wish to skip the CRD installation step, you can pass the--skip-crds flag
. Caveats:- There is no support for upgrading or deleting CRDs using Helm.
- The
--dry-run
flag ofhelm install
andhelm upgrade
is not currently supported for CRD.
- Method 2: Separate Charts: Put the CRD definition in one chart, and then put any resources that use that CRD in another chart. In this method, each chart must be installed separately.
- Method 1: Let helm Do It For You: Put CRDs in a special directory called
-
RBAC and ServiceAccount configuration should happen under separate keys as they are separate things.
rbac: # Specifies whether RBAC resources should be created create: true serviceAccount: # Specifies whether a ServiceAccount should be created create: true # The name of the ServiceAccount to use. # If not set and create is true, a name is generated using the fullname template name:
-
RBAC Resources Should be Created by Default. That is,
rbac.create
should be a boolean value controlling whether RBAC resources are created. The default should betrue
. -
serviceAccount.name
should be set to the name of theServiceAccount
to be used by access-controlled resources created by the chart. IfserviceAccount.create
istrue
, then aServiceAccount
with this name should be created. If the name is not set, then a name is generated using thefullname
template, IfserviceAccount.create
isfalse
, then it should not be created, but it should still be associated with the same resources so that manually-created RBAC resources created later that reference it will function correctly. IfserviceAccount
.create isfalse
and the name is not specified, then the defaultServiceAccount
is used. The following helper template should be used for the ServiceAccount.{{/* Create the name of the service account to use */}} {{- define "mychart.serviceAccountName" -}} {{- if .Values.serviceAccount.create -}} {{ default (include "mychart.fullname" .) .Values.serviceAccount.name }} {{- else -}} {{ default "default" .Values.serviceAccount.name }} {{- end -}} {{- end -}}
- Every defined property in values.yaml should be documented. The documentation string should begin with the name of the property that it describes, and then give at least a one-sentence description. (Repeated from Values.)
- TODO: Evaluate
helm-docs
and its automation.
- TODO: Evaluate
- Include an in-depth
README.md
, with:- Short description of the Chart.
- Any prerequisites or requirements.
- Descriptions of options in values.yaml and default values.
- Any other information that may be relevant to the installation or configuration of the chart.
- Include a short NOTES.txt, with:
- Any relevant post-installation information for the Chart.
- Instructions on how to access the application or service provided by the Chart.
- Charts must pass the linter (GitHub Action check).
- Must successfully launch with default values (
helm install
).- All pods go to the running state.
- All services have at least one endpoint.