What Is the Structure of a Helm Chart?
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
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.