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.

Chart.yaml values.yaml templates/ helpers.tpl
Helm Chart Directory Structure

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
Key Principle: A well-structured chart makes it easy to understand, maintain, and reuse. Follow the standard Helm directory structure for consistency.
Chart.yaml: Chart Metadata

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

Version of the Helm API. Use v2 for Helm v3 charts.

name

The name of the chart. This should match the directory name.

version

Semantic version of the chart. Increment when chart changes.

appVersion

Version of the application being packaged (not the chart).

dependencies

List of subcharts this chart depends on. Manage with helm dependency.

maintainers

List of people or organizations maintaining the chart.
Versioning: Use semantic versioning (MAJOR.MINOR.PATCH) for chart versions. The appVersion field indicates the version of the application being packaged, which may differ from the chart version.
values.yaml: Default Configuration

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: []
Values Best Practices:
  • 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
templates/: Kubernetes Manifests

The templates/ directory contains Go template files that generate Kubernetes manifests.

deployment.yaml

Defines the Deployment resource for the application. Uses values for replicas, image, and resource limits.

service.yaml

Defines the Service resource. Configures the service type, ports, and selectors.

ingress.yaml

Defines the Ingress resource for external access. Configures hosts, paths, and TLS.

configmap.yaml

Defines ConfigMap resources for configuration data. Uses values for key-value pairs.

secret.yaml

Defines Secret resources for sensitive data. Should reference external secrets rather than storing values.

hpa.yaml

Defines HorizontalPodAutoscaler for automatic scaling. Uses values for min/max replicas and CPU targets.
# 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 }}
Templating Tip: Use helm template to render and preview templates before installation. This helps catch errors early.
_helpers.tpl: Template Helpers

_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 }}
Helper Best Practices:
  • Define reusable functions in _helpers.tpl
  • Use include instead of template for better scoping
  • Use descriptive function names with chart prefix
  • Keep helper functions simple and focused
  • Use trunc and trimSuffix for name length limits
charts/: Subcharts and Dependencies

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
Dependency Management: Always run helm dependency update after modifying dependencies. Subcharts can override values from the parent chart.
Additional Files and Directories

README.md

Documentation for the chart. Should include installation instructions, values documentation, and examples.

NOTES.txt

Displayed after installation. Provides useful information about the deployed application (URLs, credentials, commands).

crds/

Custom Resource Definitions. These are installed before templates and are not affected by helm upgrade.

templates/tests/

Test resources for helm test. Used to verify the installation is working correctly.

values.schema.json

JSON Schema for validating values.yaml. Prevents misconfigurations during installation.

.helmignore

Specifies files to exclude when packaging the chart. Similar to .gitignore.
# 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
Frequently Asked Questions
What is the purpose of Chart.yaml?
Chart.yaml contains metadata about the chart including name, version, description, maintainers, and dependencies. It's the most important file in a Helm chart.
What is the difference between version and appVersion?
version is the chart version (semver). appVersion is the version of the application being packaged. They can be different.
Where are subcharts stored?
Subcharts are stored in the charts/ directory. They can be in the form of directories or packaged .tgz files.
What is the purpose of _helpers.tpl?
_helpers.tpl contains reusable template functions that can be used across multiple templates. It helps avoid code duplication.
What is values.schema.json used for?
values.schema.json provides JSON Schema validation for values.yaml. It helps catch misconfigurations before installation.
How do I create a new chart?
Use helm create <chart-name>. This creates a directory with the standard chart structure and example files.
Where are CRDs stored in a chart?
CRDs are stored in the crds/ directory at the root of the chart. They are installed before templates.
What is NOTES.txt used for?
NOTES.txt is displayed after installation. It provides users with useful information about the deployed application (URLs, commands, credentials).
Previous: Installing Helm Next: Chart Dependencies

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.