The fastest way to create your own Helm chart is the helm create command, which scaffolds a complete chart with Chart.yaml, values.yaml and templates for you. From there you adjust the values to your application, check the result with helm template and helm lint, and publish the finished chart to a repository so other teams can install it.
That sounds like a lot of steps, but in practice it is a manageable path from an empty folder to your own reusable package for Kubernetes. If you have ever installed someone else's chart, you already know the building blocks: Chart.yaml, values.yaml, a templates folder. Building your own chart just means filling those blocks yourself.
I run Kubernetes clusters in production and wrote the Kubernetes Practical Guide published by Rheinwerk Computing. The charts I build myself run through GitOps in real clusters, not just as an exercise. You can find all my Kubernetes articles collected on the Kubernetes page.
The anatomy of a Helm chart: Chart.yaml, values.yaml and templates
A Helm chart always consists of the same three building blocks. Chart.yaml carries the metadata: name, version, description, optionally keywords and the project's home address. values.yaml holds the default values that fill the templates, things like the image name, replica count or resource limits. The templates folder contains the actual Kubernetes manifests, but not as finished YAML, rather as a blueprint with placeholders that Helm fills in at install time.
The trick behind this: Helm separates configuration from code. Instead of hardcoding fixed values into every manifest, a template points to a value in values.yaml, for example the replica count. Whoever installs the chart can override that value without touching the template itself. That is exactly what makes a chart reusable, whether for your own development environment or for another team in your organization.
On top of these three core pieces, a freshly created chart also ships a couple of companion files: a .helmignore for files you want to exclude when packaging, and a NOTES.txt that Helm prints as a hint after installation. Both are small, but useful in practice so a chart stays understandable for other users.
Creating your own Helm chart: helm create step by step
The helm create command is the fastest entry point because it generates a working example chart for you to adjust instead of starting from zero.
helm create my-app
Afterward your folder has the following structure:
my-app/
├── Chart.yaml
├── values.yaml
├── .helmignore
├── charts/
└── templates/
├── deployment.yaml
├── service.yaml
├── ingress.yaml
├── hpa.yaml
├── serviceaccount.yaml
├── NOTES.txt
├── _helpers.tpl
└── tests/
First you open Chart.yaml and fill in the values that actually describe your chart: the name, a description, useful keywords so your chart is discoverable in a repository later, and the names of the people responsible for it. The version follows semantic versioning, so you typically start at 0.1.0.
Next comes values.yaml. The generated example chart uses Nginx as a placeholder application, so you replace the image name and tag with your own image and adjust resources, replica count and ports for your application. How deeply you structure it is up to you: a flat structure with individual keys at the top level is easy to read and works fine for small charts, while a nested structure with groups like image, service and resources pays off once a chart grows and you want to bundle related values together.
Only at the end do you touch the templates. The generated deployment.yaml already shows how a value from values.yaml gets referenced, for example {{ .Values.replicaCount }} for the number of pods. You rarely need to rewrite everything: usually it is enough to adjust the existing placeholders to your application and add new logic only where your case differs from the default example.
Writing templates and checking them with helm template
Helm templates are built on the Go templating language and look unfamiliar at first, because double curly braces {{ }} serve as placeholders. Inside these braces you can not only insert values but also write conditions and loops. One example: a deployment template can use an if condition to check whether autoscaling is enabled, and only set a fixed replica count when it is not.
Besides the values from values.yaml, Helm gives you built-in objects to work with, for example .Chart.Name for your chart's name or .Release.Name for the name of the specific installation. You need these objects, for instance, when resources inside the cluster must get unique names.
To see how a change to values.yaml or to a template plays out, without installing anything, you use helm template:
helm template my-app ./my-app
The command renders all templates locally and prints finished Kubernetes manifests to the console. That way you catch typos in the template syntax or wrong indentation before Kubernetes ever sees any of it. For every chart I work on, I check the output of helm template against what I expect, especially around conditions and loops, which get confusing fast.
Checking the chart with helm lint
helm template shows you the result, but it does not catch every mistake. For structural problems, like a missing required field in Chart.yaml or a values.yaml that does not match the templates, there is helm lint.
helm lint ./my-app
The command checks your chart against a set of rules and reports info messages, warnings and errors along with the file they came from. A missing chart description comes back as a warning, and rendered YAML that no longer parses comes back as an error. A template relying on a value that does not exist in values.yaml, on the other hand, slips through: Helm simply substitutes nothing there. When a value is genuinely mandatory, enforce it in the template with the required function. I wire helm lint and helm template into every pipeline that builds or updates a chart, because both commands run in seconds and catch exactly the mistakes that would otherwise only surface when the chart is installed into a cluster.
| Command |
Checks |
Typical finding |
helm lint |
chart structure and metadata |
missing Chart.yaml fields, invalid values.yaml |
helm template |
rendered manifests |
wrong indentation, broken conditions |
helm install --dry-run |
installation against a real cluster, without applying changes |
conflicts with existing resources |
Packaging a chart and publishing it to a repository
A chart that only lives in your local folder can be checked into Git and rolled out directly, but that way you lose the biggest benefit: other teams being able to find and install your chart without cloning your source code. That is what a repository is for.
Packaging takes one command:
helm package ./my-app
It turns your chart folder into an archive, named after the chart's name and version, for example my-app-0.1.0.tgz. You upload that archive to a Helm repository. Two approaches have become common: a classic chart repository like ChartMuseum, which accepts and serves charts through a simple HTTP API, or an OCI-compatible registry that reuses the same infrastructure as your container images. For a team that already runs a container registry, the second option is usually the smaller effort, since no extra component gets added.
| Option |
Advantage |
Disadvantage |
| ChartMuseum |
simple to set up, dedicated chart API |
one more service you have to run yourself |
| OCI registry |
reuses an existing container registry |
permissions share the registry with images, some registries need extra configuration |
Once the repository is up, you add it locally and install your chart like any other:
helm repo add my-company https://charts.example.com
helm repo update
helm install my-app my-company/my-app
Important for daily practice: every published version stays available in the repository. Anyone who needs an older version gets it through the version number in Chart.yaml, not through an extra Git branch. That is why a clean, steadily incremented version number pays off once more than one team uses your chart.
Frequently asked questions
How do I create a new Helm chart with helm create?
Running helm create <name> gives you a folder with Chart.yaml, values.yaml and a templates directory that already contains a working example for a deployment, a service and optionally an ingress. You then only adjust values and templates to your own application.
What has to be in Chart.yaml?
apiVersion, name and version are required. It is also worth adding a description, keywords, a project URL and the maintainers responsible, so others can find your chart and know who to ask.
What does helm lint do?
helm lint checks your chart for structural problems, like missing Chart.yaml fields or rendered YAML that no longer parses, before you ever install the chart. If you want required values checked structurally instead of one by one in the template, put a values.schema.json with a JSON schema next to your values.yaml; Helm then validates the supplied values against it on lint, template and install. It does not catch missing values, which you have to enforce in the template yourself with required. I run it automatically on every change to a chart.
How do I publish my own chart to a repository?
You package the chart with helm package into an archive and upload it to a chart repository like ChartMuseum or to an OCI-compatible registry. Then you add that repository locally with helm repo add and use the chart like any other with helm install.
Do I need a separate repository for every chart?
No. A repository can hold any number of charts and versions of them. Inside an organization, one shared repository for all internally developed charts is usually enough, much like a shared registry for container images.
Where to go from here
Once your chart works, it is worth revisiting the fundamentals behind it: what exactly distinguishes a chart from a plain Kubernetes manifest is covered in Helm Charts Explained: Packages for K8s. If you want a refresher on the YAML syntax behind the templates, check Kubernetes YAML: Understanding Manifests. And once your chart needs regular updates, packaging and publishing belongs in an automated pipeline, as described in Kubernetes CI/CD: the Pipeline.
My suggestion for getting started: take a small application of your own, run helm create, swap the example image for yours, and check every change with helm template and helm lint before you install. The full walkthrough, from templating syntax to publishing in a repository, is in chapter 9 of my Kubernetes Practical Guide (Rheinwerk Computing).