Basics Lesson 3 of 14

Charts

The anatomy of a Helm chart — Chart.yaml, templates/, values.yaml, helpers, and how all the pieces fit together.

🧒 Simple Explanation (ELI5)

A Helm chart is like a recipe folder. Inside you have: the recipe name and description (Chart.yaml), the default ingredients list (values.yaml), and the cooking instructions with blanks to fill in (templates/). When you "cook" (install) the chart, Helm fills in the blanks with the ingredients and produces the final dish (Kubernetes resources).

🔧 Technical Explanation

Chart Directory Structure

text
mychart/
├── Chart.yaml          # Chart metadata (name, version, description)
├── Chart.lock          # Locked dependency versions
├── values.yaml         # Default configuration values
├── values.schema.json  # JSON Schema for values validation (optional)
├── templates/          # Kubernetes manifest templates
│   ├── deployment.yaml
│   ├── service.yaml
│   ├── ingress.yaml
│   ├── configmap.yaml
│   ├── hpa.yaml
│   ├── _helpers.tpl    # Named template definitions (partials)
│   ├── NOTES.txt       # Post-install message template
│   └── tests/
│       └── test-connection.yaml
├── charts/             # Dependency subcharts (.tgz files)
├── crds/               # Custom Resource Definitions (applied before templates)
└── .helmignore         # Files to exclude from packaging

Chart.yaml — Metadata

yaml
apiVersion: v2            # v2 for Helm 3 (v1 for Helm 2)
name: myapp
description: A web application chart
type: application         # "application" or "library"
version: 1.2.0            # Chart version (SemVer)
appVersion: "3.1.0"       # App version (informational)
keywords:
  - web
  - nginx
maintainers:
  - name: DevOps Team
    email: devops@example.com
dependencies:
  - name: postgresql
    version: "12.x.x"
    repository: "https://charts.bitnami.com/bitnami"
    condition: postgresql.enabled
🔑
version vs appVersion

version is the chart's own version — bump it when you change templates or defaults. appVersion is the version of the app being deployed (e.g., nginx 1.25). They are independent.

values.yaml — Defaults

yaml
# values.yaml — sensible defaults for the chart
replicaCount: 2

image:
  repository: nginx
  tag: "1.25-alpine"
  pullPolicy: IfNotPresent

service:
  type: ClusterIP
  port: 80

ingress:
  enabled: false
  host: myapp.local

resources:
  requests:
    cpu: 100m
    memory: 64Mi
  limits:
    cpu: 250m
    memory: 128Mi

autoscaling:
  enabled: false
  minReplicas: 2
  maxReplicas: 10
  targetCPU: 70

Template Example

yaml
# 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 }}
          ports:
            - containerPort: 80
          resources:
            {{- toYaml .Values.resources | nindent 12 }}

_helpers.tpl — Named Templates

yaml
# templates/_helpers.tpl
{{- define "mychart.fullname" -}}
{{- printf "%s-%s" .Release.Name .Chart.Name | trunc 63 | trimSuffix "-" -}}
{{- end }}

{{- define "mychart.labels" -}}
app.kubernetes.io/name: {{ .Chart.Name }}
app.kubernetes.io/instance: {{ .Release.Name }}
app.kubernetes.io/version: {{ .Chart.AppVersion | quote }}
app.kubernetes.io/managed-by: {{ .Release.Service }}
helm.sh/chart: {{ printf "%s-%s" .Chart.Name .Chart.Version }}
{{- end }}

{{- define "mychart.selectorLabels" -}}
app.kubernetes.io/name: {{ .Chart.Name }}
app.kubernetes.io/instance: {{ .Release.Name }}
{{- end }}

Built-In Objects

ObjectDescriptionExample
.ValuesValues from values.yaml + overrides.Values.image.tag
.ChartChart.yaml metadata.Chart.Name, .Chart.Version
.ReleaseRelease information.Release.Name, .Release.Namespace
.TemplateCurrent template info.Template.Name
.CapabilitiesCluster capabilities.Capabilities.KubeVersion

⌨️ Hands-on

bash
# Scaffold a new chart
helm create mychart

# Explore the structure
ls -la mychart/
cat mychart/Chart.yaml
cat mychart/values.yaml
cat mychart/templates/deployment.yaml

# Render templates locally (no cluster needed)
helm template my-release ./mychart

# Render with custom values
helm template my-release ./mychart --set replicaCount=5

# Lint the chart for errors
helm lint ./mychart

🐛 Debugging Scenarios

Scenario 1: Template renders wrong value

bash
# Render and inspect what Helm produces
helm template my-release ./mychart --set image.tag=v2.0.0

# If value isn't applied, check:
# 1. Is the key path correct? (.Values.image.tag vs .Values.imageTag)
# 2. Is there a typo in values.yaml vs template reference?
# 3. Did you use --set correctly? (nested: --set image.tag=v2.0.0)

Scenario 2: "Error: template: no template found"

bash
# This happens when:
# 1. templates/ directory is empty
# 2. Template files have wrong extension (not .yaml or .tpl)
# 3. Chart directory structure is wrong

# Fix: verify structure
ls mychart/templates/  # Should have .yaml files
helm lint ./mychart    # Will report structural issues

🎯 Interview Questions

Beginner

Q: What files make up a Helm chart?

At minimum: Chart.yaml (metadata), values.yaml (defaults), and templates/ (Kubernetes manifest templates). Optional: charts/ (subcharts), crds/ (CRDs), values.schema.json (validation), _helpers.tpl (named templates), NOTES.txt (post-install message), .helmignore.

Q: What is Chart.yaml?

Chart.yaml is the metadata file for the chart. Required fields: apiVersion (v2 for Helm 3), name, version (SemVer). Important optional fields: appVersion, description, type (application or library), dependencies, keywords, maintainers.

Q: What is values.yaml?

values.yaml provides default configuration values for the chart. Templates reference these values via {{ .Values.key }}. Users can override defaults with --set key=value or -f custom-values.yaml. It defines the chart's configuration API.

Q: What is _helpers.tpl?

A file in templates/ that defines reusable named templates (partials). Files starting with _ are not rendered as Kubernetes manifests. Common helpers include label generators, name generators, and annotation builders. Referenced with {{ include "mychart.labels" . }}.

Q: What is NOTES.txt?

A template file in templates/ that is rendered and displayed to the user after helm install or helm upgrade. Used to show access instructions, URLs, next steps. Example: "Get the application URL by running: kubectl port-forward..."

Intermediate

Q: What is the difference between chart version and appVersion?

version is the chart's own version — it follows SemVer and should be bumped when templates, values, or chart metadata change. appVersion is the version of the application being deployed (e.g., nginx 1.25). They're independent: you can bump chart version without changing appVersion (e.g., fixing a template bug) or bump appVersion without changing chart version.

Q: What are the built-in objects available in Helm templates?

.Values (merged values), .Chart (Chart.yaml data), .Release (release info: Name, Namespace, IsInstall, IsUpgrade, Revision, Service), .Template (current template name/basepath), .Capabilities (cluster info: KubeVersion, APIVersions), .Files (access non-template files in the chart).

Q: What is the 'type' field in Chart.yaml?

Two types: application (default) — produces Kubernetes resources, can be installed. library — provides reusable helpers/templates only, cannot be installed directly, must be used as a dependency by application charts. Library charts are for sharing common template patterns across teams.

Q: What goes in the crds/ directory?

Custom Resource Definitions. CRDs in crds/ are applied before any templates are rendered, ensuring the CRD types exist in the cluster. Important: CRDs in this directory are only installed, never upgraded or deleted by Helm (to prevent data loss). For CRD lifecycle management, use a separate CRD chart or operator.

Q: What is .helmignore?

Similar to .gitignore, it lists file patterns to exclude when packaging the chart with helm package. Prevents test fixtures, documentation, CI files, etc. from bloating the chart archive. Default excludes: .git, .DS_Store, etc.

Scenario-Based

Q: You need to deploy the same app to dev, staging, and prod with different configs. How do you structure the chart?

One chart with environment-specific values files: values-dev.yaml (1 replica, debug logging, no ingress), values-staging.yaml (2 replicas, staging domain), values-prod.yaml (5 replicas, prod domain, HPA enabled, higher resource limits). Deploy: helm install myapp ./mychart -f values-prod.yaml -n prod.

Q: helm lint reports errors in your chart. How do you troubleshoot?

1) Run helm lint ./mychart — read exact error messages and line numbers. 2) Common issues: YAML indentation, missing closing braces {{ }}, referencing undefined values, missing required Chart.yaml fields. 3) Render templates to see output: helm template test ./mychart. 4) Add --debug for more detail. 5) Use helm lint --strict for warnings as errors.

Q: You want to include a pre-built PostgreSQL deployment as part of your chart. How?

Add it as a dependency in Chart.yaml: dependencies: - name: postgresql, version: "12.x.x", repository: "https://charts.bitnami.com/bitnami". Run helm dependency update. Override PostgreSQL values in your values.yaml under the postgresql: key. Use condition: postgresql.enabled to make it optional.

Q: Your chart has 15 templates with duplicated labels. How do you DRY it up?

Define named templates in _helpers.tpl: {{- define "mychart.labels" }} with all standard labels. Use {{ include "mychart.labels" . | nindent 4 }} in every template. Common patterns: fullname, labels, selectorLabels, serviceAccountName. This is the standard practice in the helm create scaffold.

Q: A colleague packages a chart but it's 50MB. What went wrong?

Large files (binaries, test data, node_modules, .git directory) are being included. Fix: 1) Add patterns to .helmignore. 2) Check charts/ — are there unnecessary subcharts? 3) Use helm package --debug to see included files. 4) Move static assets out of the chart (use ConfigMaps or external storage). Charts should typically be under 1MB.

🌍 Real-World Use Case

An e-commerce platform maintains a "base-microservice" chart used by 40+ services:

📝 Summary

← Back to Helm Course