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