Skip to main content
RunBook Academy

KubernetesCIV · HelmHelm

Helm chart structure — Chart.yaml, values.yaml, templates, and helpers

Advanced⏱ ~17 minhelmkubectl

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

Not yet marked complete on this device.

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. v2 for Helm 3.
  • name. Chart name.
  • type. application or library (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:

  1. Load values (default + overrides from -f and —set).
  2. Load Chart.yaml metadata.
  3. Run templates through Go’s text/template engine.
  4. Output the rendered Kubernetes manifests.
  5. 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

  1. Q1. What does `helm template` do that `helm install` does not?

  2. Q2. `helm template` output is validated by the cluster's admission controllers.

  3. 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.

  4. 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 template to see the output; verify it is what you expect.
  • Pin chart versions. Always specify --version; never use latest in production.
  • Use values files. Don’t use --set for 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.