Designing Values: Sensible Defaults, Clean Overrides

Good values.yaml is an API. Ship safe defaults, keep the override surface small, and let operators change only what they must.

Here is the thing nobody tells you early on: your values.yaml is the public API of your chart. Every key you expose is a promise you have to keep, and every key you forget to expose becomes a support ticket. Treat it like interface design, not a dumping ground.

Default for the common case, override for the exception

The defaults should install cleanly on a normal cluster with zero flags. That means a modest replicaCount, resources that fit a small node, and features like autoscaling or ingress switched off behind an enabled flag. Nobody should have to read your templates to get a working install.

Group related keys, and keep the tree shallow

Nest by concern — image, service, ingress, resources — but resist going five levels deep. Deep trees are miserable to override with --set and easy to typo. When a block is genuinely free-form (annotations, node selectors, extra env), accept a raw map and render it with toYaml so you never have to chase every possible sub-key.

Two guard rails that pay for themselves

  • required for values that have no safe default — fail the render with a clear message instead of shipping a broken manifest.
  • A JSON Schema in values.schema.json so bad input is rejected before it ever reaches the cluster.
Rule of thumb: if a value is safe to guess, give it a default. If guessing wrong is dangerous, make it required. Never make the operator supply something you could have defaulted.

Example

Example · yaml
# values.yaml -- defaults that install cleanly with zero flags
replicaCount: 2

image:
  repository: ghcr.io/acme/api
  tag: ""                 # falls back to .Chart.AppVersion in the template
  pullPolicy: IfNotPresent

resources:
  requests: { cpu: 100m, memory: 128Mi }
  limits:   { cpu: 500m, memory: 256Mi }

# Optional features are OFF by default, gated by an enabled flag
ingress:
  enabled: false
  className: nginx
  hosts: []

autoscaling:
  enabled: false
  minReplicas: 2
  maxReplicas: 10
  targetCPUUtilizationPercentage: 75

# Free-form escape hatches -- rendered verbatim with toYaml
podAnnotations: {}
extraEnv: []

When to use it

  • A chart author ships all resource requests and limits with sensible non-zero defaults so the chart passes any LimitRange policy out of the box without requiring operator input.
  • A platform team keeps the override surface small by grouping advanced tuning options under an 'advanced' key that most operators can ignore, while keeping common options at the top level.
  • An operator reads a well-designed values.yaml to understand exactly what the chart supports without needing to read the templates, treating the file as the chart's user manual.

More examples

Self-documenting values structure

Shows a values.yaml that comments each group, sets safe non-zero resource defaults, and uses an empty tag convention tied to appVersion.

Example · yaml
# values.yaml
# replicaCount: number of application pods (increase for HA)
replicaCount: 2

image:
  repository: myregistry/myapp
  # tag defaults to Chart.appVersion when left empty
  tag: ""
  pullPolicy: IfNotPresent

resources:
  # Safe defaults that pass common LimitRange policies
  requests:
    cpu: 100m
    memory: 128Mi
  limits:
    cpu: 500m
    memory: 512Mi

Feature-flag pattern

Groups optional features under their own enabled flag so operators opt in explicitly, keeping simple installs minimal.

Example · yaml
# values.yaml — optional features gated by enabled flags
ingress:
  enabled: false
  className: nginx
  host: app.example.com
  tls: []

serviceMonitor:
  enabled: false
  interval: 30s

autoscaling:
  enabled: false
  minReplicas: 2
  maxReplicas: 10
  targetCPUUtilizationPercentage: 70

Validate values with required

Uses required to enforce that operators supply the two values that cannot have safe defaults, failing fast with clear guidance.

Example · yaml
# templates/deployment.yaml snippet
env:
  - name: DB_URL
    value: {{ required "db.url is required — set it with --set db.url=..." .Values.db.url | quote }}
  - name: SECRET_KEY
    valueFrom:
      secretKeyRef:
        name: {{ required "existingSecret must name a Secret" .Values.existingSecret | quote }}
        key: secret-key

Discussion

  • Be the first to comment on this lesson.