Helm chart structure — Chart.yaml, values.yaml, templates, and helpers
What you'll learn
- Navigate a Helm chart structure
- Read and modify Chart.yaml, values.yaml, templates
- Use named templates and helpers
- Apply the operational discipline of understanding chart structure before installing
Prerequisites
Verified against Kubernetes 1.34.x · kubeadm 1.34.x · kubectl 1.34.x · etcd 3.6.x · CoreDNS 1.11.x · containerd 1.7.x / 2.x · 2026-08-16
A Helm chart is a directory of files that, together, describe a deployable Kubernetes application. This lesson walks the chart structure, the rendering process, and the operational discipline.
The chart structure
flowchart LR
A["mychart/"] --> B[Chart.yaml]
A --> C[values.yaml]
A --> D["templates/"]
A --> E["charts/"]
A --> F[README.md]
A --> G[LICENSE]
A --> H[values.schema.json]
D --> D1[deployment.yaml]
D --> D2[service.yaml]
D --> D3[_helpers.tpl]
D --> D4[NOTES.txt]
The chart structure:
- Chart.yaml — metadata: name, version (chart version), appVersion (the application version), dependencies, maintainers, keywords.
- values.yaml — default values for the chart’s templates.
- templates/ — Kubernetes manifests with Go templating. The templates are rendered with values to produce the final manifests.
- charts/ — subcharts (dependencies) packaged as tarballs.
- README.md — documentation.
- LICENSE — license file.
- values.schema.json — JSON schema for validating values.
- _helpers.tpl — named templates (helpers) for reuse across templates.
Chart.yaml
apiVersion: v2
name: mychart
description: A Helm chart for Kubernetes
type: application
version: 1.2.3 # chart version (SemVer)
appVersion: "1.16.0" # application version
dependencies:
- name: postgresql
version: 12.1.0
repository: https://charts.bitnami.com/bitnami
condition: postgresql.enabled
keywords:
- web
- api
maintainers:
- name: Example
email: ops@example.com
Chart.yaml fields:
- apiVersion.
v2for Helm 3. - name. Chart name.
- type.
applicationorlibrary(library is for helpers, not standalone install). - version. Chart version (SemVer).
- appVersion. The application version (e.g., PostgreSQL 16.0).
- dependencies. Other charts this chart depends on.
values.yaml
replicaCount: 3
image:
repository: myapp
tag: "1.0.0"
pullPolicy: IfNotPresent
service:
type: ClusterIP
port: 80
resources:
requests:
cpu: 100m
memory: 128Mi
limits:
cpu: 500m
memory: 512Mi
values.yaml is the default values for the chart.
Installs can override values via --set or a custom
values file (-f my-values.yaml).
Templates
# templates/deployment.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
name: {{ include "mychart.fullname" . }}
labels:
{{- include "mychart.labels" . | nindent 4 }}
spec:
replicas: {{ .Values.replicaCount }}
selector:
matchLabels:
{{- include "mychart.selectorLabels" . | nindent 6 }}
template:
metadata:
labels:
{{- include "mychart.selectorLabels" . | nindent 8 }}
spec:
containers:
- name: {{ .Chart.Name }}
image: "{{ .Values.image.repository }}:{{ .Values.image.tag }}"
imagePullPolicy: {{ .Values.image.pullPolicy }}
resources:
{{- toYaml .Values.resources | nindent 12 }}
The templates use Go templating:
{{ .Values.x }}accesses values from values.yaml.{{ .Chart.Name }}accesses chart metadata.{{ include "mychart.fullname" . }}includes a named template from _helpers.tpl.{{- toYaml .Values.resources | nindent 12 }}converts a value to YAML and indents it.
Helpers (_helpers.tpl)
{{/* templates/_helpers.tpl */}}
{{- define "mychart.fullname" -}}
{{- if .Values.fullnameOverride -}}
{{- .Values.fullnameOverride | trunc 63 | trimSuffix "-" -}}
{{- else -}}
{{- $name := default .Chart.Name .Values.nameOverride -}}
{{- printf "%s-%s" .Release.Name $name | trunc 63 | trimSuffix "-" -}}
{{- end -}}
{{- end -}}
{{- define "mychart.labels" -}}
helm.sh/chart: {{ .Chart.Name }}-{{ .Chart.Version | replace "+" "_" }}
app.kubernetes.io/name: {{ .Chart.Name }}
app.kubernetes.io/instance: {{ .Release.Name }}
app.kubernetes.io/version: {{ .Chart.AppVersion }}
app.kubernetes.io/managed-by: {{ .Release.Service }}
{{- end -}}
Helpers are named templates that are reused across manifests. Common helpers:
fullname— generates the full resource name.labels— generates standard labels.selectorLabels— generates labels for selectors.
The rendering process
flowchart LR
A[values.yaml + overrides] --> B[Helm template engine]
C[Chart.yaml] --> B
D["templates/"] --> B
B -->|render| E[Kubernetes manifests]
E -->|kubectl apply| F[Cluster]
The rendering process:
- Load values (default + overrides from -f and —set).
- Load Chart.yaml metadata.
- Run templates through Go’s text/template engine.
- Output the rendered Kubernetes manifests.
- Apply the manifests to the cluster (during install/upgrade).
helm template myrelease mychart/ \
--set replicaCount=5 \
--set image.tag=2.0.0
The output is the rendered YAML. Review it before installing.
Helm 3.8+ OCI registries
Helm 3.8 added support for OCI registries. Charts can be stored in OCI-compliant registries (Harbor, GHCR, ECR, GCR, ACR) instead of traditional Helm repositories.
helm chart save mychart/ oci://registry.example.com/charts/mychart:1.0.0
helm chart push oci://registry.example.com/charts/mychart:1.0.0
helm install myrelease oci://registry.example.com/charts/mychart --version 1.0.0
OCI registries are the modern way to distribute charts; many providers (AWS, GCP, Azure) now offer OCI-compatible Helm registries.
Quiz
Knowledge check · 4 questions
Q1. What does `helm template` do that `helm install` does not?
Q2. `helm template` output is validated by the cluster's admission controllers.
Q3. Explain why a chart deploys a subchart version that Chart.yaml does not name, and bring the packaged dependencies back into line.
The in-house `billing` chart declares `postgresql` version `13.2.0` from the Bitnami repository in its `dependencies`, but the Pods come up running an older PostgreSQL than expected. `helm dependency list billing/` reports `postgresql 12.1.0 ok`, `billing/charts/` contains a committed `postgresql-12.1.0.tgz`, and `Chart.lock` was last written fourteen months ago.
Q4. What is the difference between `version` and `appVersion` in Chart.yaml, and which of the two does `--version` on `helm install` select?
Passing score: 75%. Answers are checked in this browser.
The operational discipline
Helm charts in production rest on five non-negotiable elements:
- Read the chart before installing. Never install a chart you have not read; review the templates for security implications.
- Render before installing. Use
helm templateto see the output; verify it is what you expect. - Pin chart versions. Always specify
--version; never uselatestin production. - Use values files. Don’t use
--setfor complex values; use a values file for review and version control. - Test in staging first. A chart that has not been tested in staging may fail in production.
Helm charts are powerful but require discipline. The discipline is to read, render, pin, and test.