A Helm chart is just a directory. Helm cares about the names and locations of files inside it — there is no central registration. helm create my-app scaffolds the canonical layout.
The Canonical Layout
my-app/
├── Chart.yaml # Chart metadata
├── values.yaml # Default configuration
├── values.schema.json # Optional JSON schema validating values
├── README.md # Human-readable documentation
├── LICENSE # Optional licence file
├── .helmignore # Files to exclude when packaging
├── charts/ # Sub-charts (dependencies)
├── crds/ # CustomResourceDefinitions (installed once)
└── templates/
├── NOTES.txt # Printed after install/upgrade
├── _helpers.tpl # Reusable template partials
├── deployment.yaml
├── service.yaml
├── ingress.yaml
├── serviceaccount.yaml
├── hpa.yaml
└── tests/
└── test-connection.yaml
Chart.yaml
The identity card of the chart. The minimum is name, version, apiVersion. Modern charts use apiVersion: v2 which adds dependencies, type, and kubeVersion support.
apiVersion: v2
name: my-app
description: My production web application
type: application # or 'library' (no resources, only helpers)
version: 1.4.2 # Chart version — bump on every change
appVersion: "2.7.0" # Application version — string, quoted
kubeVersion: ">=1.27" # Cluster version constraint
icon: https://example.com/icon.png
keywords: [web, api, microservice]
home: https://example.com
sources: [https://github.com/acme/my-app]
maintainers:
- name: Platform Team
email: platform@acme.io
dependencies:
- name: postgresql
version: "12.x.x"
repository: "oci://registry-1.docker.io/bitnamicharts"
condition: postgresql.enabled
Crucial distinction:
- version — the chart's own SemVer. Bumped every time the chart changes, even for typo fixes.
- appVersion — the version of the software the chart deploys. Doesn't have to be SemVer; quote it.
values.yaml
The default configuration. Users override at install time via -f my-values.yaml or --set key=value. Keep the file small, well-commented, and grouped:
replicaCount: 2
image:
repository: ghcr.io/acme/my-app
pullPolicy: IfNotPresent
tag: "" # Empty = falls back to .Chart.AppVersion
service:
type: ClusterIP
port: 80
ingress:
enabled: false
className: nginx
hosts:
- host: chart-example.local
paths: ["/"]
resources:
limits: { cpu: 500m, memory: 512Mi }
requests: { cpu: 100m, memory: 128Mi }
autoscaling:
enabled: false
minReplicas: 2
maxReplicas: 10
values.schema.json
Optional, but strongly recommended for charts other people will consume. Helm validates the merged values against the schema before rendering — fail fast on typos and bad types:
{
"$schema": "https://json-schema.org/draft/2020-12/schema",
"type": "object",
"required": ["image"],
"properties": {
"replicaCount": { "type": "integer", "minimum": 1 },
"image": {
"type": "object",
"required": ["repository"],
"properties": {
"repository": { "type": "string" },
"tag": { "type": "string" }
}
}
}
}
templates/
Every .yaml file here is rendered through the Go template engine and the output applied to the cluster. Special files:
NOTES.txt— text rendered and printed at the end ofhelm install. Use it to show URLs, next-step commands, secrets to fetch.- Files starting with
_(e.g.,_helpers.tpl) — partials only. Not rendered as resources. Used todefinereusable template blocks called viainclude. templates/tests/— manifests with the annotation"helm.sh/hook": test. Run viahelm test <release>.
charts/
Holds sub-charts (dependencies). Either committed by hand (vendored) or pulled by helm dependency update from the registries listed in Chart.yaml. We will cover dependencies in lesson 5.
crds/
A special directory: anything inside crds/ is installed once on first install and never modified on upgrade or delete. This protects CRDs from being accidentally removed (which would cascade-delete every custom resource of that type). For evolving CRDs use a separate "CRDs chart" and accept the operational responsibility.
.helmignore
Like .gitignore but for helm package. Patterns matched here are excluded from the .tgz. The default ignores Git artefacts, IDE files, and OS noise. Add anything large or sensitive (test data, design docs).
Built-in Objects
Inside templates Helm exposes a small set of top-level objects. Memorise these — they are the API:
| Object | Contents |
|---|---|
.Values | Merged values (defaults + overrides) |
.Chart | Fields from Chart.yaml |
.Release | Release-level info: Name, Namespace, IsInstall, IsUpgrade, Revision |
.Files | Access to non-template files in the chart (configs, scripts) |
.Capabilities | Cluster info: KubeVersion, APIVersions |
.Template | Info about the current template file |
In the next lesson we put these to work.