10 Helm Chart Development Best Practices
Develop reliable, maintainable, high-quality Helm charts

This post is based on extensive experience developing Helm charts as a product, combining technical Helm skills with a product mindset.
In this post, we cover the development (not operations) of an upstream Helm chart, which means it will be used by other companies (customers) rather than just internally.
Some points here could be too much if you use the Helm chart within your company, but they are essential when the chart is used by 3rd parties, and you donât have direct contact with them.
Yet itâs also useful to be aware of these practices, especially for Platform Engineering, where the chart is consumed by multiple internal teams.
Letâs go đž
Note: This post is mainly based on Helm 3 experience. But it should work with Helm 4, since most of whatâs written focuses on general software engineering practices.ToC
1. Product Mindset
To be honest, before diving into any technical details, there is a critical aspect Iâd like to emphasize ... product mindset! When your chart is upstream, you need to treat Helm chart development as you would any other software product, not just a helper tool.
This affects a lot of your decisions and actions. You should think about your actions as a product manager, not just an engineer. If you have never had product management training or courses, I highly recommend starting.
2. Mastering Essentials
Start with the basics: Look at the default chart created by the
helm createcommand and learn about whatâs considered the defaults.Extend the basics: Read the docs! Especially the essential pages like âNamed Templatesâ, âBuilt-in Objectsâ, and âTemplate Function Listâ. They have a lot of useful information, and I keep going back to them. Especially the function list.
Read the project best practices: Itâs a good idea to read the Chart Best Practices Guide by the Helm project, as this post extends that guide with more details.
3. Chart Architecture and Design
Donât use sub-charts: The Helm sub-charts are really limited and serve a narrow use case. The biggest issue with it is that, by design, each sub-chart cannot access the other sub-chartsâ data! You will run into many issues if your chart has more than one component/application.
Use Helm chart libraries: No need to reinvent the wheel. Many Helm charts are used solely as libraries, with common, reusable tasks across charts. The most famous one is the Bitnami Common chart.
Use proper nesting: Keep the values file shallow but not too shallow! Donât put everything at one level; well-structured chart parameters improve readability. Finally, if you are adding a new key, using a map makes it easier to extend later than a single value or an array. Remember, Helm doesnât merge arrays by default, so an extra values file will override the whole array in the default values file.
# Good (balanced).
image:
repository: nginx
tag: "1.25"
pullPolicy: IfNotPresent
# Bad, too nested (deep).
application:
deployment:
container:
image:
repository:
name: nginx
tag: "1.25"
pull:
policy: IfNotPresent
# Bad, too shallow (flat).
image: nginx
imageTag: "1.25"
imagePullPolicy: IfNotPresentUse flat repository: If you need to handle multiple versions of the chart at the same time (e.g., due to SLA or support agreement), itâs way much better to use a flat structure instead of using branches. Putting all versions in the main branch makes life much easier, especially for CI/CD changes. It was a nightmare to work with each version in its own branch.
4. Security and Compliance
Enable security by default: Nowadays, you should treat security as the norm, not an extra step. Follow all security best practices when creating Kubernetes manifests. For example, use a read-only filesystem, drop capabilities, and run as a non-root user by default.
Sign and verify the chart: Integrity is a critical security aspect. Use Cosign to sign your chart daily and throughout the entire SDLC. The Cosign keyless sign made it super easy to apply this security practice.
5. Templating
Use _helpers.tpl as a central boilerplate: Keep the manifests clean and utilize
_helpers.tplas a one-stop shop for processed data (you can split it into smaller files if needed).Use environment variables from ConfigMap: Donât clutter your manifests by setting environment variables in Pod definitions; instead, use envFrom to load them from a ConfigMap.
Include external files in ConfigMaps: Donât embed content in ConfigMaps; include them externally for better linting and syntax highlighting! Remember, the included filename should start with an underscore (â_â) so that Helm doesnât render it directly.
# templates/foo/configmap.yaml
apiVersion: v1
kind: ConfigMap
[...]
data:
application.yaml: |
{{- (include (print $.Template.BasePath "/foo/files/_application.yaml") $) | indent 4 }}Auto-reload Pods on ConfigMap/Secret change: Use hash functions in the Pods annotations to auto-reload the Pods once the ConfigMap/Secret changes. This guarantees that the Deployment will automatically pick up the new configuration.
# templates/foo/deployment.yaml
apiVersion: apps/v1
kind: Deployment
[...]
spec:
template:
metadata:
annotations:
checksum/config: {{ include (print $.Template.BasePath "/foo/configmap.yaml") . | sha256sum }}Use named templates with parameters: In most of the charts, you will find that the named templates have one input. However, you can define multiple parameters for the named templates, which gives you great flexibility close to real programming languagesâ functions.
{{/*
Render component data as YAML.
Example:
{{ include "renderComponentData" (dict "componentName" "foo" "componentValues" .Values.foo "context" $) }}
*/}}
{{- define "renderComponentData" -}}
{{- ## .context.Release.Name ## -}}
{{- .componentName -}}: {{- .componentValues -}}
{{- end -}}Use tpl (wisely): Using the
tplfunction helps you to reference dynamic values and reduce redundancy. So you can easily cross-reference within your values.yaml file. However, donât use it with a large chunk of input, as it could increase the chartâs complexity and slow rendering.
# values.yaml
hostname: "{{ .Release.Name }}.example.com"# If the release name is called "foo", then it will be rendered as "foo.example.com".
{{ tpl (toYaml .Values.hostname) $ }}Use safe nullable nested values: One of the nice tricks all the time. When a key isnât guaranteed to exist, use this syntax to handle nullable nested values and fallback gracefully without throwing rendering errors.
# This will not fail if foo, bar, or baz are not defined.
{{ $bazValue := (((.Values.foo).bar).baz) }}Allow users to include raw manifests: Given that Helm is a templating engine, you cannot support all use cases. Hence, allow users to include additional manifests to easily extend the chart.
# values.yaml
extraManifests:
- |
apiVersion: v1
kind: ConfigMap
metadata:
name: foo
data:
foo.json: '{"data": "value"}'Fail fast: Use the constraints file to enforce requirements or mutually exclusive rules, preventing a defective deployment.
# constraints.tpl
{{- if (.Values.name) }}
{{- $errorMessage := printf "[foo][deprecation] %s %s"
"The parameter \".name\" has been removed in favor of \".fullname\"."
"For more details, please check the official documentation."
-}}
{{ printf "\n%s" $errorMessage | trimSuffix "\n"| fail }}
{{- end }}6. Testing and Validation
Build strong CI pipelines: Your pipelines should incorporate all software engineering practices, including linting, code style, unit tests, and integration tests (yes, for Helm charts!). Those pipelines will be the bones of the Helm chart's development, as use cases increase, and without them, it will be hard to make changes with confidence to create high-quality Helm charts.
Generate a rendered snapshot: In your Git repo, store a rendered copy of the chart manifests to make it easier to detect differences across changes. Those files are known as golden files.
Run unit test: Relying solely on the golden files is insufficient because they only reflect changes to the default values. So you need to test the chart as code for every value in the values file to ensure everything works as expected when the chart is applied. Think about it like a unit test in a programming language. For that, you can use a Golang library like Terratest, which has modules for helm and k8s.
Run end-to-end test: Even though Helm has its own test method, itâs very limited and can only be used for basic validation. Think about end-to-end test suites like Playwright, where you can test the components in different scenarios as well as the actual user flow.
Enable schema validation: Another important safeguard is to define the schema of your values.yaml file. This will show an error to the users when they misspell the key name or value type. There are 2 ways to do that: the first is simple and limited, and involves setting the types in the file
values.schema.yaml.Or directly auto-generatevalues.schema.json(basically an OpenAPI schema file) using a tool such as bitnami/readme-generator-for-helm, which will save you time and keep the schema up to date.Test chart packaged: For a long time (and till writing these words in 2026), Helm CLI behavior differs due to many bugs when installing from a packaged chart (from the
tgzfile) or an unpackaged chart (directly from the chart directory). As a rule of thumb, always test with the user's actual case, so test against a packaged chart.
7. Documentation and Annotation
Auto-generate docs: You can easily generate the README file or part of it with all available chart parameters using bitnami/readme-generator-for-helm (the same tool used to generate the schema). Itâs super useful and helps keep your Helm chart docs up to date.
Set chart metadata: Add any critical or useful information in the
Chart.yamlfile-like âkeywordsâ, âmaintainersâ, âannotationsâ, etc. An important annotation to add is the Helm CLI version used to test and package the chart (the Helm project is known for bugs and incompatibilities across minor versions). Also, add Artifact Hub annotations, which are important when you share the chart later.
8. Packaging and Distribution
Decouple chart and app versioning: Usually, when you have an internal chart, itâs easier to use the chart version the same as the application version. But when you have an upstream chart used by thousands of users, itâs better to give the chart its own version, so you can fix issues without waiting for the next application release.
Automate the release process: Use release-please to automate your chart releases; it works well with Helm and can make your life easier when you work with multiple charts at once.
Promote artifacts, donât rebuild: This might be considered unnecessary for Helm. But in reality, you should always do that for any artifact! With each build, there is a risk of change, so always promote the exact artifact across environments (e.g., Dev -> Staging -> Prod) rather than rebuilding it from source each time, to ensure you test the exact artifact users will use.
Package chart in OCI format: Historically, the chart artifact was a
tgztar archive, but Helm supportsOCI(Open Container Initiative) artifacts, which you can store in a modern container registry. This should be the standard now.Publish to Artifact Hub: Adding your chart to Artifact Hub makes it easier for end users to discover and use. Even if your chart repository is public, end users should never use the chart directly from the repo, as itâs primarily used for development. As mentioned above, there may be hidden bugs, or you may take extra steps when generating the chart, which the user will miss if they try to install from the Git repository directly.
9. Misc
Use Helm hooks (wisely): Helm has a hooks mechanism that allows you to run certain lifecycle tasks. For example, I used hooks to automatically generate initial credentials/secrets and store them in a K8S Secret object. You donât want that part of the chart included to avoid accidentally deleting the secrets if the chart is uninstalled.
Utilize Post-Rendering: In some cases, you may need to use the post-rendering feature to fix issues beyond your control, such as a bug in the Helm CLI. You can also combine Helm and Kustomize; they are friends, not enemies. Use Helm to generate the base manifests and use Kustomize for last-mile environment overlays (via Helmâs post-rendering features or wrapping Helm in a kustomization.yaml).
Maintain backward compatibility: If your chart is used by many downstream users, always remember that you never break backward compatibility without a clear deprecation cycle. Always try to add the new feature, then remove the deprecated ones after a couple of releases.
10. Get ready for Helm 4
As mentioned at the beginning of this post, this post is based on Helm 3 experience (released on November 13, 2019), but most of whatâs written here should work with Helm 4, which was released a couple of months ago (released on November 12, 2025) ... thatâs exactly 6 years difference.
On the chart level, Helm 4 maintains backward compatibility, so it should work smoothly with the charts created by Helm 3. But at the operational level, a couple of changes are needed, such as renaming the CLI flags, which will require review in the CI/CD pipelines.
The notable change in Helm 4 that I really like is the new plugin system with three plugin types: CLI plugins, getter plugins, and post-renderer plugins, plus a system that enables new plugin types to customize additional core functionality.
The migration to Helm v4 shouldnât be a big deal for most cases, but it should be done sooner rather than later.
Thanks for your time, and hope you enjoy the journey! Here is a kickoff checklist:
đ Read the frequently asked questions page (it has much valuable information).
â Star the project repo on GitHub (for better visibility).
đ Join the Telegram group (for interactive communication).
Happy DevOpsing âžď¸



Great blog!!!