ai-superpower/.ai/instructions/skills/helm.instructions.md
moilanik cb402009fe feat(helm): forbid hardcoded namespaces and release names
Add hard rule: charts must be fully agnostic about installation target.
- Never hardcode namespace in any template — use .Release.Namespace or omit
- Never hardcode release name — use .Release.Name / fullname helper
- Enforce IaC principle: deployer decides where, chart describes what
2026-03-10 08:43:43 +02:00

8.1 KiB
Raw Blame History

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

🚫 Never Hardcode Namespace, Release Name, or Installation-Specific Values

A Helm chart must be fully agnostic about where and how it is installed.

This is a hard rule — no exceptions.

Namespace

Never hardcode a namespace in any template. The namespace is set by the deployer at install time.

# ❌ FORBIDDEN — always
metadata:
  namespace: my-app
  namespace: production
  namespace: default

# ✅ Required — or omit namespace entirely and let Helm inject it
metadata:
  namespace: {{ .Release.Namespace }}

If a resource must reference another namespace (e.g. a NetworkPolicy peer), expose it as a required value:

metadata:
  namespace: {{ required "global.namespace is required" .Values.global.targetNamespace }}

Release Name

Never hardcode a release name. Use .Release.Name to derive resource names:

# ❌ FORBIDDEN
name: my-app-service
name: myapp-ingress

# ✅ Required
name: {{ include "mychart.fullname" . }}
# or
name: {{ .Release.Name }}-service

Why — IaC Principle

The chart is infrastructure code. It must be deployable to any cluster, any namespace, any environment, by any deployer — without editing the chart itself. Installation-specific decisions belong exclusively in values.<installation>.yaml in the deployment repo.

❌ Wrong: chart decides where it lives
✅ Right: deployer decides where it lives — chart only describes what it is

🏠 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.):

  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
# 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 (*, {, }) → .AsConfig handles 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.yaml contains defaults valid for every installation; it never contains installation-specific data
  • values.<installation>.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 arent 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: dont add it. If its 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
  • required validation fires when values are missing