Lesson 16 Chart Validation & Linting
Theme:

Chart Validation & Linting

A systematic approach to catching Helm chart errors before they reach a cluster.

Introduction

A Helm chart that looks correct in your editor can still fail at install time. Template logic may reference a nil value, a YAML key may be misspelled, or a rendered manifest may use a deprecated Kubernetes API version. These failures are expensive: they interrupt deployments, block CI pipelines, and erode confidence in your release process.

The solution is a layered validation strategy. Each layer catches a different class of error, and together they form a pipeline that moves from fast, cheap checks to slow, thorough ones. This lesson covers every tool in that pipeline: helm lint, helm template, JSON Schema validation, kubeconform, and the ct chart-testing tool.

helm lint: Structural Analysis

helm lint is the first line of defence. It parses your chart directory and checks for structural problems without rendering templates against actual values. Think of it as a compiler check for Helm charts.

# Basic lint
helm lint ./mychart

# Lint with a specific values file
helm lint ./mychart -f staging-values.yaml

# Strict mode: warnings become errors
helm lint ./mychart --strict

The linter checks several things:

Warnings vs Errors

Helm lint produces two severity levels. Errors cause a non-zero exit code and indicate problems that will prevent installation. Warnings indicate best-practice violations that will not block installation but may cause issues later. In CI pipelines, always use --strict to treat warnings as errors — this prevents deprecated APIs and missing metadata from slipping through.

Common lint errors: Missing Chart.yaml fields, invalid YAML in values.yaml, template functions that reference undefined helpers, and charts missing a templates/ directory.

helm template: Local Rendering

helm template renders all templates locally and outputs the resulting YAML to stdout. Unlike helm lint, it evaluates every template expression against a full values tree, so it catches runtime errors like nil pointer dereferences and type mismatches.

# Render with default values
helm template my-release ./mychart

# Render a specific template file
helm template my-release ./mychart -s templates/deployment.yaml

# Show verbose debug output on errors
helm template my-release ./mychart --debug

The --debug flag is essential for troubleshooting. When a template fails, it prints the full computed values and shows the exact template file and line number where the error occurred. Without --debug, you often get a cryptic "nil pointer evaluating interface" message with no location information.

Common template errors

Server-Side Dry Run

helm install --dry-run renders templates and optionally sends them to the Kubernetes API server for validation. There are two modes:

ModeCommandWhat it validates
Client-sidehelm install --dry-runTemplate rendering only. No cluster needed.
Server-sidehelm install --dry-run=serverRendering + API schema validation, admission webhooks, CRD checks.

Server-side dry run is the most thorough validation Helm offers natively. It catches invalid field names, wrong API versions for your cluster, and even admission webhook rejections. The downside is that it requires cluster access, which may not be available in all CI environments.

values.schema.json: Input Validation

A values.schema.json file placed at the chart root defines a JSON Schema that Helm validates against every time it processes values — during lint, template, install, and upgrade. This is the best way to document and enforce what your chart expects from its consumers.

{
  "$schema": "https://json-schema.org/draft/2020-12/schema",
  "type": "object",
  "required": ["replicaCount", "image"],
  "properties": {
    "replicaCount": {
      "type": "integer",
      "minimum": 1,
      "maximum": 20,
      "description": "Number of pod replicas"
    },
    "image": {
      "type": "object",
      "required": ["repository", "tag"],
      "properties": {
        "repository": {
          "type": "string",
          "minLength": 1
        },
        "tag": {
          "type": "string",
          "pattern": "^v?[0-9]+\\.[0-9]+\\.[0-9]+$"
        },
        "pullPolicy": {
          "type": "string",
          "enum": ["Always", "IfNotPresent", "Never"],
          "default": "IfNotPresent"
        }
      }
    },
    "service": {
      "type": "object",
      "properties": {
        "type": {
          "type": "string",
          "enum": ["ClusterIP", "NodePort", "LoadBalancer"]
        },
        "port": {
          "type": "integer",
          "minimum": 1,
          "maximum": 65535
        }
      }
    }
  }
}

The schema supports all standard JSON Schema keywords: required, type, enum, pattern (regex), minimum/maximum, minLength/maxLength, nested properties, and additionalProperties. You can also add description fields to document each value — some tools render these as help text.

Schema strictness trade-off: A very strict schema (with "additionalProperties": false everywhere) prevents consumers from passing extra values. This improves safety but reduces flexibility. Start permissive and tighten over time as your chart stabilises.

Fixing schema validation errors

When schema validation fails, Helm prints every violation at once. Common patterns:

kubeconform: Kubernetes Schema Validation

Even if your chart renders valid YAML, the YAML may not be a valid Kubernetes manifest. A misspelled field name like contianers (instead of containers) will pass helm template without complaint but fail at install time. kubeconform solves this by validating rendered YAML against the official Kubernetes OpenAPI schemas.

# Install
brew install kubeconform

# Validate rendered chart against K8s 1.29 schema
helm template my-release ./mychart | kubeconform \
  -strict \
  -kubernetes-version 1.29.0 \
  -summary

# Validate with CRD schemas from a URL
helm template my-release ./mychart | kubeconform \
  -strict \
  -schema-location default \
  -schema-location 'https://raw.githubusercontent.com/datreeio/CRDs-catalog/main/{{.Group}}/{{.ResourceKind}}_{{.ResourceAPIVersion}}.json'

Key flags: -strict rejects YAML with unknown fields, -kubernetes-version targets a specific cluster version, and -schema-location adds CRD schemas for custom resources.

kubeconform vs kubeval: kubeval was the original tool but is no longer maintained. kubeconform is its actively maintained successor with better performance and CRD support. Always prefer kubeconform for new projects.

ct (chart-testing) for CI

The ct tool, maintained by the Helm project, is designed for testing charts in CI. It detects which charts changed in a pull request, lints them, optionally installs them into a test cluster (usually kind), and runs helm test.

# Install
brew install chart-testing

# Lint changed charts (compares against target branch)
ct lint --target-branch main

# Lint and install changed charts
ct lint-and-install --target-branch main

Configuration lives in a ct.yaml file at the repo root:

# ct.yaml
target-branch: main
chart-dirs:
  - charts
helm-extra-args: --timeout 120s
validate-maintainers: false

ct is the standard in open-source chart repositories and integrates seamlessly with GitHub Actions via the helm/chart-testing-action.

Pre-commit Hooks

Catching errors before they even reach CI saves time. Pre-commit hooks run validation on every commit:

# .pre-commit-config.yaml
repos:
  - repo: https://github.com/gruntwork-io/pre-commit
    rev: v0.1.23
    hooks:
      - id: helmlint

  - repo: local
    hooks:
      - id: helm-lint
        name: Helm Lint
        entry: bash -c 'helm lint ./mychart --strict'
        language: system
        files: 'mychart/.*'
        pass_filenames: false

      - id: helm-kubeconform
        name: Kubeconform
        entry: bash -c 'helm template test ./mychart | kubeconform -strict'
        language: system
        files: 'mychart/.*'
        pass_filenames: false

The Full Validation Pipeline

Combining all the tools into a single pipeline gives you comprehensive coverage. Each stage catches a different class of error, and the pipeline is ordered from fastest to slowest:

  1. helm lint --strict — structural and syntax errors (milliseconds)
  2. helm template — rendering errors, nil pointers, scope issues (milliseconds)
  3. kubeconform — invalid K8s manifests, wrong API versions, typos (seconds)
  4. helm install --dry-run=server — admission webhooks, CRD validation (seconds, requires cluster)
  5. ct lint-and-install — full integration test with helm test (minutes)

Fail fast: if helm lint fails, do not run the remaining stages. This keeps CI feedback times short for common errors.

Summary: Treat chart validation like code testing — layer your checks from fast static analysis to slow integration tests. Use values.schema.json to document and enforce your chart's API. Run --strict everywhere in CI. Adopt kubeconform and ct early; they pay for themselves on the first prevented production incident.