# Helm Chart Development Guidelines ## πŸ” Before Creating Anything New **ALWAYS search the workspace for existing implementations first.** Before writing a new template, config, or resource: 1. Search the workspace for similar existing files 2. If found, migrate/adapt it β€” don't invent a new structure 3. Only create from scratch if nothing exists --- ## 🏠 Resource Ownership **Every Kubernetes resource must have exactly one owner. Never duplicate.** - If a chart creates a resource (e.g. Traefik creates IngressClass), don't create it elsewhere - Before adding a template for a resource, verify no other chart/subchart already manages it ``` ❌ Wrong: subchart A creates IngressClass AND subchart B creates IngressClass βœ… Right: one chart owns IngressClass, others do not touch it ``` --- ## 🧹 values.yaml Hygiene **values.yaml must only contain values that templates actually use.** Before adding a value: - Verify at least one template references `.Values.` - If no template uses it β†’ don't add it - Empty string defaults (`key: ""`) that only serve as documentation are forbidden β€” use README instead **Umbrella chart values.yaml:** - Contains only subchart defaults that override upstream chart defaults - All environment-specific values belong in the deployment repo - Must NOT contain empty placeholders **Subchart values.yaml:** - Contains only values the subchart's own templates reference - Required values (validated with `required`) must NOT have empty defaults β€” absence should trigger the error --- ## βœ… required vs defaults Use `required` for values that MUST come from the deployer. Do NOT put empty defaults in values.yaml for these. ```yaml # Good: fails loudly at install if not provided name: {{ required "ingress.className is required" .Values.ingress.className }} # Bad: silently passes empty string, fails later in obscure way name: {{ .Values.ingress.className | default "" }} ``` --- ## πŸ“¦ Subchart Dependency Conditions Use `condition:` in `Chart.yaml` to enable/disable components. Use flat booleans, not nested `.enabled`: ```yaml # Chart.yaml dependencies: - name: keycloak repository: "file://../components/keycloak" version: "0.2.0" condition: components.keycloak # values (Good) components: keycloak: true traefik: false # values (Avoid) keycloak: enabled: true ``` --- ## ⏱️ Hook Ordering Resources that depend on other resources must use `helm.sh/hook` with `hook-weight` to ensure correct install order. Established ordering convention: - `hook-weight: "10"` β€” infrastructure prerequisites (e.g. ClusterIssuer, IngressClass) - `hook-weight: "15"` β€” resources that depend on prerequisites (e.g. Certificate) ```yaml metadata: annotations: "helm.sh/hook": post-install,post-upgrade "helm.sh/hook-weight": "10" ``` --- ## πŸ“„ Configuration Files over values.yaml **Prefer `files/` + ConfigMap over defining configuration in values.yaml.** When a component needs configuration (CASC, realm config, ini files, etc.): 1. Put the config file in `files/` directory 2. Create a ConfigMap that reads it with `.AsConfig` + `tpl` 3. Use `{{ .Values.global.xxx }}` expressions inside the config file for dynamic values ```yaml # templates/configmap.yaml kind: ConfigMap apiVersion: v1 metadata: name: my-component-config data: {{ (tpl (.Files.Glob "files/*").AsConfig . ) | nindent 2 }} ``` ```yaml # files/config.yaml β€” contains Helm template expressions server: url: https://{{ .Values.global.domain }} env: {{ .Values.global.environment }} ``` **Why:** Config files are readable, diffable, and version-controlled as real files. Large configs in values.yaml become unmaintainable and hard to review. **When `.AsConfig` + `tpl` is required:** - Config file contains `{{ }}` template expressions β†’ always use `.AsConfig` + `tpl` - Config file contains special characters (`*`, `{`, `}`) β†’ `.AsConfig` handles escaping safely ```yaml # Never use plain Files.Get on files with template expressions or special chars: {{ tpl (.Files.Get "files/config.json") . }} # ❌ breaks on * characters # Always use AsConfig: {{ tpl (.Files.Glob "files/config.json").AsConfig . }} # βœ… ``` --- ## πŸ”„ Dependency Caching After editing any subchart template or values, cached charts in `charts/` will be stale: ```bash rm -rf charts/ Chart.lock helm dependency update . ``` Always rebuild before `helm template` testing when subchart files have changed. --- ## πŸŽ‚ Umbrella Chart Pattern The standard structure is an umbrella chart that integrates multiple component charts. ``` my-umbrella/ β”œβ”€β”€ Chart.yaml ← declares component charts as dependencies β”œβ”€β”€ values.yaml ← static baseline: shared across all installations β”œβ”€β”€ templates/ ← only cross-cutting resources that no subchart owns └── charts/ ← built by helm dependency update ``` **Two-file values layering:** | File | Where it lives | Purpose | |---|---|---| | `values.yaml` | umbrella chart repo | static baseline, same across all IAC installations | | `values..yaml` | IAC / deployment repo | installation-specific overrides and required parameters | - `values.yaml` contains defaults valid for every installation; it never contains installation-specific data - `values..yaml` provides what that specific installation requires β€” credentials, hostnames, sizing, feature flags - Required values (no sensible default) use `required` in templates and have **no entry** in `values.yaml` β€” their absence triggers a clear error at install time --- ## ⚑ KISS β€” Start Minimal, Add Only When Asked **Do not add knobs, flags, or options that aren’t needed right now.** - Build the minimum that meets the stated functional requirement - Do not anticipate future needs with extra values, conditions, or template logic - Complexity is easy to add; it is hard to remove once deployed - AI may **suggest** what might be needed later β€” but must not implement it without explicit instruction ```yaml # ❌ Wrong: adding a flag β€œjust in case” features: enableFutureThing: false # not needed yet, adding anyway # βœ… Right: don’t add it. If it’s needed, the developer will ask. ``` **When in doubt, leave it out.** A value that exists but is never used is noise. A template condition that is never toggled is dead code. Raise the question with the developer instead of silently implementing it. --- ## πŸ§ͺ Testing Templates Standard verification command: ```bash helm template . \ --set global.key=value \ ... \ 2>&1 | grep "^kind:" | sort | uniq -c ``` Verify: - Expected resource kinds appear - No `Error:` lines - `required` validation fires when values are missing