DEV Community

cloud-sky-ops
cloud-sky-ops

Posted on

Post 6/10 — Helm Fundamentals Done Right: Chart Architecture, Values Schema, and Reuse

If you're reading this blog I'd guess you're familiar with Helm and want to utilize if effectively. In case you aren't, I got you. Helm is a powerful packaging tool that help scale the Kubernetes manifest in a structured manner. I have highlighted the importance of Helm in some of my previous blogs and I'm covering some extra bits in this one. Just want my DevOps Engineer to not miss out on this simple yet effective tool. Do check my previous releases on Helm on my profile.

1) Executive Summary

Helm charts can be a blessing or a mess — depending on how disciplined your structure is. In this post, I’ll show how to design charts that scale: clean folder layout, validated values, reusable helpers, and consistent environment overlays.
Think of this as the point where “copy-pasted YAML” evolves into a templatized, validated, and reusable release artifact.


2) Prereqs

You should already be comfortable with:

  • kubectl basics and manifests (Deployment, Service, Ingress).
  • helm install, upgrade, and uninstall.
  • YAML indentation and Go template syntax ({{ .Values.image.repository }}).

If not, go back to Post 5 where we replaced Ingress with Gateway API — that’s the level of confidence you need.


3) Concepts

a) Chart layout & sane defaults

A Helm chart is just structured YAML + templates + metadata.

Default layout:

mychart/
├── Chart.yaml
├── values.yaml
├── values.schema.json
├── templates/
│   ├── deployment.yaml
│   ├── service.yaml
│   └── _helpers.tpl
└── charts/
Enter fullscreen mode Exit fullscreen mode

Why it matters:

  • Chart.yaml defines metadata and dependencies.
  • values.yaml holds configuration knobs (default values).
  • templates/ defines the actual Kubernetes manifests.
  • _helpers.tpl hosts shared snippets like names and labels.

Decision cue:
If you can’t read your chart structure at a glance — it’s probably too magical. Keep it explicit.


b) values.schema.json validation

This file enforces structure and type-checking for your values.yaml.
Without it, Helm silently ignores typos — your app may deploy with wrong configs.

Before → After

Before:

# values.yaml
image:
  repo: nginx
  tag: latest  # typo: should be 'repository'
Enter fullscreen mode Exit fullscreen mode

After:

// values.schema.json
{
  "$schema": "https://json-schema.org/draft-07/schema#",
  "properties": {
    "image": {
      "type": "object",
      "properties": {
        "repository": { "type": "string" },
        "tag": { "type": "string" }
      },
      "required": ["repository"]
    }
  }
}
Enter fullscreen mode Exit fullscreen mode

Now, helm lint will fail fast if someone mis-keys a field.

Decision cue:
If multiple people edit values.yaml, a schema is mandatory.


c) Helpers/partials & library charts

Avoid repetition. Use helpers (_helpers.tpl) for common names, labels, and annotations.

# _helpers.tpl
{{- define "app.fullname" -}}
{{ printf "%s-%s" .Release.Name .Chart.Name }}
{{- end -}}
Enter fullscreen mode Exit fullscreen mode

Then reuse it:

metadata:
  name: {{ include "app.fullname" . }}
Enter fullscreen mode Exit fullscreen mode

When multiple services share common patterns (e.g., serviceaccount.yaml, probes, labels), extract them into a library chart.
Library charts have no manifests — just helpers and templates that other charts can import.

Decision cue:
When you copy the same YAML block in more than two charts — extract it into a helper or library.


d) Environment overlays strategy

Instead of maintaining values-dev.yaml, values-stg.yaml, and values-prod.yaml in chaos, organize by overlays:

values/
  base.yaml
  dev.yaml
  stg.yaml
  prod.yaml
Enter fullscreen mode Exit fullscreen mode

Each extends the base:

helm install myapp . -f values/base.yaml -f values/prod.yaml
Enter fullscreen mode Exit fullscreen mode

Best practice:

  • Keep common knobs in base.yaml.
  • Only override what truly differs across envs.
  • Avoid env-specific conditionals in templates (if .Values.env == "prod" → smells).

4) Mini-Lab — From Raw Manifests to Chart

  1. Start with raw YAML:
   kubectl create deploy web --image=nginx --dry-run=client -o yaml > deployment.yaml
   kubectl expose deploy web --port=80 --dry-run=client -o yaml > service.yaml
Enter fullscreen mode Exit fullscreen mode
  1. Create a chart:
   helm create webapp
Enter fullscreen mode Exit fullscreen mode
  1. Move manifests: replace defaults with your generated files under templates/.
  2. Add schema validation: Create values.schema.json to validate image & replica fields.
  3. Extract helpers: Move naming logic to _helpers.tpl.
  4. Test locally:
   helm template webapp .
   helm lint webapp
   helm install webapp . --dry-run
Enter fullscreen mode Exit fullscreen mode

Result: a reusable, validated chart with clear structure.

Diagram


5) Cheatsheet

Command Purpose
helm show values <chart> Display default values
helm lint . Validate chart + schema
helm template . Render YAML locally
helm upgrade --install <release> . -f values/prod.yaml Idempotent deploy
Schema tip: Run jq . values.schema.json to quickly validate structure

6) Pitfalls

Pitfall Why It Hurts Fix
Values drift Each team edits values.yaml differently. Enforce schema + overlays.
Over-templating Logic explodes inside YAML. Keep logic minimal; pre-compute in CI/CD.
No library reuse Duplication everywhere. Move shared blocks to _helpers.tpl or library charts.
Missing schema Silent deployment errors. Add values.schema.json early.

7) Wrap-up — Next: Shipping Charts with Confidence: Lint, Tests, Diff, OCI & Provenance (Post 7)

We now have clean, validated, reusable Helm charts that can be tested in isolation.
Next, in Post 7, we’ll cover how to ship charts with confidence: Lint, Tests, Diff, OCI & Provenance.

Helm is powerful when it’s predictable.
Do it right once, and every future deployment becomes safer, faster, and auditable. Thanks for sticking around till the end, drop your comments and feedback below.


Top comments (0)