What Is the Structure of a Helm Chart?

intermediate|helmdevopssrebackend developerCKACKAD
TL;DR

A Helm chart is a directory structure containing templates, default values, metadata, and optional dependencies. The key files are Chart.yaml (metadata), values.yaml (defaults), and template files in the templates/ directory that generate Kubernetes manifests.

Detailed Answer

A Helm chart is a packaged set of Kubernetes resource definitions. Understanding its structure is the foundation for creating, modifying, and debugging Helm-based deployments.

Directory Structure

my-chart/
├── Chart.yaml           # Chart metadata (required)
├── values.yaml          # Default configuration values (required)
├── charts/              # Dependency charts
├── templates/           # Template files
│   ├── _helpers.tpl     # Template helper definitions
│   ├── deployment.yaml  # Deployment template
│   ├── service.yaml     # Service template
│   ├── ingress.yaml     # Ingress template
│   ├── configmap.yaml   # ConfigMap template
│   ├── hpa.yaml         # HPA template
│   ├── serviceaccount.yaml
│   ├── NOTES.txt        # Post-install usage notes
│   └── tests/           # Helm test definitions
│       └── test-connection.yaml
├── .helmignore          # Files to exclude from packaging
└── README.md            # Chart documentation

Chart.yaml

The chart's identity and metadata:

apiVersion: v2              # Helm 3 uses v2
name: my-web-app
version: 1.2.3              # Chart version (follows semver)
appVersion: "3.0.0"         # Application version
description: A web application chart
type: application            # application or library
keywords:
  - web
  - nginx
maintainers:
  - name: Platform Team
    email: platform@example.com
dependencies:
  - name: postgresql
    version: "12.x.x"
    repository: "https://charts.bitnami.com/bitnami"
    condition: postgresql.enabled
  - name: redis
    version: "17.x.x"
    repository: "https://charts.bitnami.com/bitnami"
    condition: redis.enabled

values.yaml

Default values that templates reference:

replicaCount: 3

image:
  repository: my-web-app
  tag: "3.0.0"
  pullPolicy: IfNotPresent

service:
  type: ClusterIP
  port: 80

ingress:
  enabled: true
  className: nginx
  hosts:
    - host: app.example.com
      paths:
        - path: /
          pathType: Prefix
  tls:
    - secretName: app-tls
      hosts:
        - app.example.com

resources:
  requests:
    cpu: "100m"
    memory: "128Mi"
  limits:
    cpu: "500m"
    memory: "512Mi"

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

postgresql:
  enabled: true
  auth:
    database: myapp

Template Files

Templates use Go template syntax to generate manifests:

# templates/deployment.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
  name: {{ include "my-chart.fullname" . }}
  labels:
    {{- include "my-chart.labels" . | nindent 4 }}
spec:
  {{- if not .Values.autoscaling.enabled }}
  replicas: {{ .Values.replicaCount }}
  {{- end }}
  selector:
    matchLabels:
      {{- include "my-chart.selectorLabels" . | nindent 6 }}
  template:
    metadata:
      labels:
        {{- include "my-chart.selectorLabels" . | nindent 8 }}
    spec:
      containers:
        - name: {{ .Chart.Name }}
          image: "{{ .Values.image.repository }}:{{ .Values.image.tag }}"
          imagePullPolicy: {{ .Values.image.pullPolicy }}
          ports:
            - containerPort: 8080
          resources:
            {{- toYaml .Values.resources | nindent 12 }}

_helpers.tpl

Reusable template snippets:

# templates/_helpers.tpl

{{- define "my-chart.name" -}}
{{- .Chart.Name | trunc 63 | trimSuffix "-" }}
{{- end }}

{{- define "my-chart.fullname" -}}
{{- if .Values.fullnameOverride }}
{{- .Values.fullnameOverride | trunc 63 | trimSuffix "-" }}
{{- else }}
{{- printf "%s-%s" .Release.Name (include "my-chart.name" .) | trunc 63 | trimSuffix "-" }}
{{- end }}
{{- end }}

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

{{- define "my-chart.selectorLabels" -}}
app.kubernetes.io/name: {{ include "my-chart.name" . }}
app.kubernetes.io/instance: {{ .Release.Name }}
{{- end }}

Built-in Template Objects

| Object | Description | |--------|-------------| | .Values | Values from values.yaml and overrides | | .Chart | Chart.yaml metadata | | .Release | Release information (name, namespace, revision) | | .Capabilities | Cluster capabilities (API versions, K8s version) | | .Template | Current template information |

Working with Charts

# Create a new chart
helm create my-chart

# Render templates locally (debug)
helm template my-release ./my-chart -f custom-values.yaml

# Install with custom values
helm install my-release ./my-chart \
  -f production-values.yaml \
  --set replicaCount=5

# Package the chart
helm package ./my-chart

# Push to a registry
helm push my-chart-1.2.3.tgz oci://registry.example.com/charts

Conditional Templates

# Only create Ingress if enabled
{{- if .Values.ingress.enabled }}
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
  name: {{ include "my-chart.fullname" . }}
spec:
  # ... ingress spec
{{- end }}

Managing Dependencies

# Download dependencies
helm dependency update ./my-chart

# List dependencies
helm dependency list ./my-chart

Dependencies are downloaded into charts/ and can be conditionally enabled via values:

# values.yaml
postgresql:
  enabled: true    # Include PostgreSQL subchart
redis:
  enabled: false   # Exclude Redis subchart

Chart Testing

# templates/tests/test-connection.yaml
apiVersion: v1
kind: Pod
metadata:
  name: {{ include "my-chart.fullname" . }}-test
  annotations:
    "helm.sh/hook": test
spec:
  containers:
    - name: test
      image: busybox:1.36
      command: ['wget', '--spider', 'http://{{ include "my-chart.fullname" . }}:{{ .Values.service.port }}']
  restartPolicy: Never
helm test my-release

Why Interviewers Ask This

Helm is the de facto package manager for Kubernetes. Understanding chart structure is essential for creating, customizing, and troubleshooting Helm deployments.

Common Follow-Up Questions

What is the difference between Chart.yaml and values.yaml?
Chart.yaml contains chart metadata (name, version, dependencies). values.yaml contains default configuration values that templates reference with {{ .Values.key }}.
What are Helm template helpers and where do they live?
Helper templates are defined in templates/_helpers.tpl. They contain reusable template snippets (like labels) referenced via {{ include "chartname.labels" . }}.
How do chart dependencies work?
Dependencies are declared in Chart.yaml under the dependencies section. Running helm dependency update downloads them into the charts/ directory.

Key Takeaways

  • Chart.yaml defines the chart's identity, version, and dependencies.
  • values.yaml provides defaults that users can override with -f or --set.
  • Templates in templates/ use Go templating to generate Kubernetes manifests dynamically.

Related Questions

You Might Also Like