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:
- Chart.yaml validity — required fields like
apiVersion,name, andversionmust be present and correctly typed. - Template parsing — Go template syntax must be valid. Unclosed actions, mismatched braces, and undefined functions are caught here.
- Deprecated APIs — references to removed Kubernetes API versions (e.g.,
extensions/v1beta1) trigger warnings. - Icon and metadata — missing
iconfield or emptydescriptiongenerate warnings.
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.
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
- Nil pointer — accessing
.Values.foo.barwhenfoois not defined. Fix with{{ if .Values.foo }}...{{ end }}or provide defaults. - Wrong indentation — using
indentinstead ofnindent, or passing the wrong count. The rendered YAML will be invalid. - Scope errors — inside a
rangeblock,.refers to the loop item, not the top-level scope. Use$to access the root.
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:
| Mode | Command | What it validates |
|---|---|---|
| Client-side | helm install --dry-run | Template rendering only. No cluster needed. |
| Server-side | helm install --dry-run=server | Rendering + 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.
"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:
- Type mismatch — passing a string where an integer is expected (e.g.,
replicaCount: "3"). YAML without quotes is fine:replicaCount: 3. - Enum violation — a value not in the allowed list. Check capitalisation;
"always"is not"Always". - Pattern mismatch — the value does not match the regex. Remember that JSON Schema patterns are anchored implicitly in some implementations but not others — use explicit
^and$anchors. - Missing required field — a required key is absent from values. Either add the default to
values.yamlor remove it fromrequired.
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.
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:
- helm lint --strict — structural and syntax errors (milliseconds)
- helm template — rendering errors, nil pointers, scope issues (milliseconds)
- kubeconform — invalid K8s manifests, wrong API versions, typos (seconds)
- helm install --dry-run=server — admission webhooks, CRD validation (seconds, requires cluster)
- 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.
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.