Helm Chart Structure
A complete guide to Helm chart structure covering Chart.yaml, values.yaml, templates, helpers, charts directory, and best practices for organizing Helm charts.
A Helm chart follows a specific directory structure. Understanding this structure is essential for chart development.
# Chart Directory Structure
my-app/
├── Chart.yaml # Chart metadata (name, version, description)
├── values.yaml # Default configuration values
├── values-*.yaml # Environment-specific values (optional)
├── charts/ # Dependent charts (subcharts)
├── crds/ # Custom Resource Definitions
├── templates/ # Kubernetes resource templates
│ ├── _helpers.tpl # Template helper functions
│ ├── deployment.yaml # Deployment manifest template
│ ├── service.yaml # Service manifest template
│ ├── ingress.yaml # Ingress manifest template
│ ├── configmap.yaml # ConfigMap template
│ ├── secret.yaml # Secret template
│ ├── hpa.yaml # HorizontalPodAutoscaler template
│ ├── NOTES.txt # Post-installation notes
│ └── tests/ # Test resources
└── README.md # Documentation
Chart.yaml contains metadata about the chart. It's the most important file in a Helm chart.
# Chart.yaml Example
apiVersion: v2
name: my-app
description: A Helm chart for my application
type: application
version: 1.2.3 # Chart version (semver)
appVersion: 2.5.0 # Application version
home: https://example.com
icon: https://example.com/icon.png
maintainers:
- name: John Doe
email: john@example.com
- name: Jane Doe
email: jane@example.com
keywords:
- web
- microservice
sources:
- https://github.com/example/my-app
dependencies:
- name: postgresql
version: 11.x.x
repository: https://charts.bitnami.com/bitnami
condition: postgresql.enabled
alias: db
- name: redis
version: 16.x.x
repository: https://charts.bitnami.com/bitnami
condition: redis.enabled
annotations:
category: Database
apiVersion
v2 for Helm v3 charts.name
version
appVersion
dependencies
helm dependency.maintainers
appVersion field indicates the version of the application being packaged, which may differ from the chart version.
values.yaml defines default configuration values for the chart. Users can override these values during installation.
# values.yaml
# Global settings
global:
imagePullSecrets: []
storageClass: standard
# Application settings
replicaCount: 3
image:
repository: nginx
tag: latest
pullPolicy: IfNotPresent
pullSecrets: []
imagePullPolicy: IfNotPresent
nameOverride: ""
fullnameOverride: ""
service:
type: ClusterIP
port: 80
targetPort: 8080
annotations: {}
ingress:
enabled: false
className: ""
annotations: {}
hosts:
- host: app.example.com
paths:
- path: /
pathType: Prefix
tls: []
resources:
requests:
memory: "64Mi"
cpu: "250m"
limits:
memory: "128Mi"
cpu: "500m"
autoscaling:
enabled: false
minReplicas: 1
maxReplicas: 10
targetCPUUtilizationPercentage: 80
# Environment-specific values
environment: production
# ConfigMap data
configMap:
key1: value1
key2: value2
# Secrets (should not be stored in values.yaml)
# Use external secret management instead
secrets:
existingSecret: "my-secret"
# Pod settings
podAnnotations: {}
podSecurityContext: {}
securityContext: {}
# Node selector and tolerations
nodeSelector: {}
tolerations: []
affinity: {}
# Persistence
persistence:
enabled: false
storageClass: ""
accessMode: ReadWriteOnce
size: 1Gi
# Additional volumes and mounts
extraVolumes: []
extraVolumeMounts: []
- Organize values logically with clear sections
- Use descriptive keys and comments
- Set sensible defaults for all configurable options
- Document all values in README.md
- Use environment-specific values files (values-dev.yaml, values-prod.yaml)
- Never store secrets in values.yaml
The templates/ directory contains Go template files that generate Kubernetes manifests.
deployment.yaml
service.yaml
ingress.yaml
configmap.yaml
secret.yaml
hpa.yaml
# templates/deployment.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
name: {{ include "my-app.fullname" . }}
labels:
{{- include "my-app.labels" . | nindent 4 }}
spec:
replicas: {{ .Values.replicaCount }}
selector:
matchLabels:
{{- include "my-app.selectorLabels" . | nindent 6 }}
template:
metadata:
labels:
{{- include "my-app.selectorLabels" . | nindent 8 }}
annotations:
{{- toYaml .Values.podAnnotations | nindent 8 }}
spec:
containers:
- name: {{ .Chart.Name }}
image: "{{ .Values.image.repository }}:{{ .Values.image.tag | default .Chart.AppVersion }}"
imagePullPolicy: {{ .Values.image.pullPolicy }}
ports:
- containerPort: {{ .Values.service.targetPort }}
name: http
env:
- name: ENVIRONMENT
value: {{ .Values.environment | quote }}
{{- with .Values.extraEnv }}
{{- toYaml . | nindent 8 }}
{{- end }}
resources:
{{- toYaml .Values.resources | nindent 10 }}
{{- with .Values.securityContext }}
securityContext:
{{- toYaml . | nindent 10 }}
{{- end }}
volumeMounts:
{{- toYaml .Values.extraVolumeMounts | nindent 8 }}
volumes:
{{- toYaml .Values.extraVolumes | nindent 6 }}
{{- with .Values.nodeSelector }}
nodeSelector:
{{- toYaml . | nindent 8 }}
{{- end }}
{{- with .Values.affinity }}
affinity:
{{- toYaml . | nindent 8 }}
{{- end }}
{{- with .Values.tolerations }}
tolerations:
{{- toYaml . | nindent 8 }}
{{- end }}
helm template to render and preview templates before installation. This helps catch errors early.
_helpers.tpl contains reusable template functions that can be used across multiple templates.
# templates/_helpers.tpl
{{- define "my-app.name" -}}
{{- default .Chart.Name .Values.nameOverride | trunc 63 | trimSuffix "-" }}
{{- end }}
{{- define "my-app.fullname" -}}
{{- if .Values.fullnameOverride }}
{{- .Values.fullnameOverride | trunc 63 | trimSuffix "-" }}
{{- else }}
{{- $name := default .Chart.Name .Values.nameOverride }}
{{- if contains $name .Release.Name }}
{{- .Release.Name | trunc 63 | trimSuffix "-" }}
{{- else }}
{{- printf "%s-%s" .Release.Name $name | trunc 63 | trimSuffix "-" }}
{{- end }}
{{- end }}
{{- end }}
{{- define "my-app.chart" -}}
{{- printf "%s-%s" .Chart.Name .Chart.Version | replace "+" "_" | trunc 63 | trimSuffix "-" }}
{{- end }}
{{- define "my-app.labels" -}}
helm.sh/chart: {{ include "my-app.chart" . }}
{{ include "my-app.selectorLabels" . }}
{{- if .Chart.AppVersion }}
app.kubernetes.io/version: {{ .Chart.AppVersion | quote }}
{{- end }}
app.kubernetes.io/managed-by: {{ .Release.Service }}
{{- end }}
{{- define "my-app.selectorLabels" -}}
app.kubernetes.io/name: {{ include "my-app.name" . }}
app.kubernetes.io/instance: {{ .Release.Name }}
{{- end }}
{{- define "my-app.serviceAccountName" -}}
{{- if .Values.serviceAccount.create }}
{{- default (include "my-app.fullname" .) .Values.serviceAccount.name }}
{{- else }}
{{- default "default" .Values.serviceAccount.name }}
{{- end }}
{{- end }}
{{- define "my-app.imagePullSecrets" -}}
{{- if .Values.image.pullSecrets }}
imagePullSecrets:
{{- toYaml .Values.image.pullSecrets | nindent 2 }}
{{- end }}
{{- end }}
- Define reusable functions in _helpers.tpl
- Use
includeinstead oftemplatefor better scoping - Use descriptive function names with chart prefix
- Keep helper functions simple and focused
- Use
truncandtrimSuffixfor name length limits
The charts/ directory contains dependent charts (subcharts). Dependencies are managed in Chart.yaml.
# Chart.yaml with dependencies
dependencies:
- name: postgresql
version: 11.x.x
repository: https://charts.bitnami.com/bitnami
condition: postgresql.enabled
- name: redis
version: 16.x.x
repository: https://charts.bitnami.com/bitnami
condition: redis.enabled
# Download dependencies
helm dependency update
# Directory structure after update
charts/
├── postgresql-11.1.0.tgz
└── redis-16.0.0.tgz
# Conditional dependency (disabled by default)
# values.yaml
postgresql:
enabled: true
redis:
enabled: false
# Alias for dependency
dependencies:
- name: postgresql
version: 11.x.x
repository: https://charts.bitnami.com/bitnami
alias: db
condition: db.enabled
# Use alias in values
db:
enabled: true
postgresqlPassword: secret
helm dependency update after modifying dependencies. Subcharts can override values from the parent chart.
README.md
NOTES.txt
crds/
templates/tests/
helm test. Used to verify the installation is working correctly.values.schema.json
.helmignore
# values.schema.json example
{
"$schema": "http://json-schema.org/draft-07/schema#",
"type": "object",
"properties": {
"replicaCount": {
"type": "integer",
"minimum": 1,
"maximum": 10
},
"image": {
"type": "object",
"properties": {
"repository": {
"type": "string"
},
"tag": {
"type": "string"
}
},
"required": ["repository", "tag"]
}
}
}
# .helmignore example
# Patterns to ignore when packaging
.git/
*.swp
*.tmp
.DS_Store
*.test
*.iml
.idea/
*/tests/
*/ci/
*/charts/*.tgz
version is the chart version (semver). appVersion is the version of the application being packaged. They can be different.charts/ directory. They can be in the form of directories or packaged .tgz files._helpers.tpl contains reusable template functions that can be used across multiple templates. It helps avoid code duplication.helm create <chart-name>. This creates a directory with the standard chart structure and example files.crds/ directory at the root of the chart. They are installed before templates.Understanding Helm chart structure is fundamental to creating maintainable and reusable charts. Follow the standard directory structure and best practices for consistent, high-quality charts.