Catching errors before they reach the cluster
helm lint checks a chart for structural issues without rendering templates or contacting a cluster.
# Basic lint
helm lint ./mychart
# Lint with custom values
helm lint ./mychart -f staging-values.yaml
# Strict mode: treat warnings as errors
helm lint ./mychart --strict
What it checks:
apiVersion, name, version)values.yaml--strict. Always use --strict in CI pipelines.Renders all templates to YAML without installing. Perfect for inspecting output and piping to other tools.
# Render all templates
helm template my-release ./mychart
# Render with values and show only one template
helm template my-release ./mychart \
-f prod-values.yaml \
-s templates/deployment.yaml
# Debug mode: verbose errors with computed values
helm template my-release ./mychart --debug
Tip: --debug prints the full values tree and shows the exact line where a template error occurs — invaluable for tracking down nil pointer errors.
helm install --dry-run sends rendered manifests to the Kubernetes API server for validation — without actually creating resources.
# Client-side only (no cluster needed)
helm install my-release ./mychart --dry-run
# Server-side validation (requires cluster access)
helm install my-release ./mychart --dry-run=server --debug
Place a JSON Schema file at the chart root to validate values.yaml at lint, template, install, and upgrade time.
{
"$schema": "https://json-schema.org/draft/2020-12/schema",
"type": "object",
"required": ["replicaCount", "image"],
"properties": {
"replicaCount": {
"type": "integer",
"minimum": 1,
"maximum": 20
},
"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"]
}
}
}
}
}
Key features: required, type, enum, pattern, minimum/maximum, and nested properties.
values.yaml with an error:
replicaCount: "three" # Should be integer
image:
repository: nginx
tag: latest # Fails pattern ^v?[0-9]+\.[0-9]+\.[0-9]+$
pullPolicy: Sometimes # Not in enum
Error output from helm install:
Error: values don't meet the specifications of the schema(s):
- replicaCount: Invalid type. Expected: integer, given: string
- image.tag: Does not match pattern '^v?[0-9]+\.[0-9]+\.[0-9]+$'
- image.pullPolicy: image.pullPolicy must be one of: Always, IfNotPresent, Never
lint, template, install, and upgrade. No flags needed.Validates rendered YAML against the Kubernetes OpenAPI schemas — catches invalid fields, wrong API versions, and typos.
# Install kubeconform
brew install kubeconform # or download binary
# Validate rendered output
helm template my-release ./mychart | kubeconform \
-strict \
-kubernetes-version 1.29.0 \
-summary
# Example output
# stdin - Deployment my-release-app is valid
# stdin - Service my-release-svc is valid
# Summary: 2 resources found - Valid: 2, Invalid: 0, Errors: 0
Why not just dry-run? kubeconform works offline, runs fast, and can target a specific K8s version — ideal for CI where you may not have cluster access.
The recommended validation pipeline runs four stages in order:
# .github/workflows/helm-validate.yml
jobs:
validate:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Lint
run: helm lint ./mychart --strict
- name: Template
run: helm template test ./mychart -f ci/values.yaml > rendered.yaml
- name: Kubeconform
run: |
kubeconform -strict -kubernetes-version 1.29.0 rendered.yaml
- name: Chart Testing (ct)
uses: helm/chart-testing-action@v2
with:
command: lint-and-install
ct (chart-testing) detects changed charts in a monorepo, lints them, installs into a kind cluster, and runs helm test. It is the standard for chart CI in open-source projects.
Pipeline order: lint → template → kubeconform → install (fast failures first, expensive tests last).