New skill files: - clean-code.instructions.md — naming, functions, classes, error handling, formatting, DRY/KISS/SOLID, Helm YAML conventions - clean-architecture.instructions.md — dependency rule, layers, boundaries, SOLID foundation, Helm as outermost layer - helm.instructions.md — resource ownership, values hygiene, required vs defaults, umbrella chart pattern, two-file values layering, KISS principle, hook ordering, config files pattern, dependency caching, template testing Register all three in ai-root-instructions.md skills list and routing table. Remove .ai from .gitignore — .ai/ is the product in this repo and must be tracked; the apply.sh skip-by-marker mechanism prevents changes to other repos.
6.7 KiB
Helm Chart Development Guidelines
🔍 Before Creating Anything New
ALWAYS search the workspace for existing implementations first.
Before writing a new template, config, or resource:
- Search the workspace for similar existing files
- If found, migrate/adapt it — don't invent a new structure
- 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.<key> - 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.
# 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:
# 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)
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.):
- Put the config file in
files/directory - Create a ConfigMap that reads it with
.AsConfig+tpl - Use
{{ .Values.global.xxx }}expressions inside the config file for dynamic values
# templates/configmap.yaml
kind: ConfigMap
apiVersion: v1
metadata:
name: my-component-config
data: {{ (tpl (.Files.Glob "files/*").AsConfig . ) | nindent 2 }}
# 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 (
*,{,}) →.AsConfighandles escaping safely
# 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:
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.<installation>.yaml |
IAC / deployment repo | installation-specific overrides and required parameters |
values.yamlcontains defaults valid for every installation; it never contains installation-specific datavalues.<installation>.yamlprovides what that specific installation requires — credentials, hostnames, sizing, feature flags- Required values (no sensible default) use
requiredin templates and have no entry invalues.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
# ❌ 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:
helm template <release-name> . \
--set global.key=value \
... \
2>&1 | grep "^kind:" | sort | uniq -c
Verify:
- Expected resource kinds appear
- No
Error:lines requiredvalidation fires when values are missing