12.4 Creating Your Own Chart
⏱️ 5 min read · 8 min hands-on · 🟡 Intermediate
TL;DR:
helm create my-chartscaffolds a complete, working chart with example templates. Strip it down to what you need and fill in your app’s specifics. The scaffold follows best practices (labels, helpers, NOTES.txt) from the start.
After this section you will be able to:
- Scaffold a new custom chart structure with
helm create- Parameterize application manifests with reusable variables and templated labels
- Validate chart syntax and formatting with
helm lint
Scaffolding a New Chart
# Create a new chart with all the boilerplate
helm create my-app
# What gets created:
tree my-app
my-app/
├── Chart.yaml
├── values.yaml
├── charts/
└── templates/
├── deployment.yaml
├── hpa.yaml
├── ingress.yaml
├── service.yaml
├── serviceaccount.yaml
├── _helpers.tpl
├── NOTES.txt
└── tests/
└── test-connection.yaml
The scaffolded chart deploys nginx by default — replace values and templates for your app.
Minimal Chart Walkthrough
After helm create my-app, the key changes to make:
1. Update Chart.yaml:
name: my-app
description: My Application
version: 0.1.0
appVersion: "1.0.0"
2. Update values.yaml defaults:
replicaCount: 2
image:
repository: myrepo/my-app # ← Your actual image
tag: "1.0.0"
pullPolicy: IfNotPresent
service:
type: ClusterIP
port: 8080 # ← Your app's port
env: # Custom values section
LOG_LEVEL: info
DB_HOST: postgres-svc
resources:
requests:
cpu: "100m"
memory: "128Mi"
limits:
cpu: "500m"
memory: "256Mi"
3. Update templates/deployment.yaml to use your custom env section:
# In the containers section, add env vars from values:
env:
{{- range $key, $val := .Values.env }}
- name: {{ $key }}
value: {{ $val | quote }}
{{- end }}
Validating Your Chart
# Lint for common errors
helm lint my-app/
# Expected output (clean):
# ==> Linting my-app/
# [INFO] Chart.yaml: icon is recommended
# 1 chart(s) linted, 0 chart(s) failed
# Preview rendered YAML (catch template errors)
helm template test-release my-app/ | head -80
# Preview with custom values
helm template test-release my-app/ --values my-prod-values.yaml
# Validate against the cluster API without installing
helm template test-release my-app/ | kubectl apply --dry-run=server -f -
Environment-Specific Values Files
# Directory structure for multi-environment deployment
config/
├── values-base.yaml # Shared defaults
├── values-dev.yaml # Dev overrides
├── values-staging.yaml # Staging overrides
└── values-prod.yaml # Prod overrides
# values-prod.yaml — only what differs from base
replicaCount: 5
resources:
requests:
cpu: "500m"
memory: "512Mi"
limits:
cpu: "2"
memory: "1Gi"
ingress:
enabled: true
host: app.mycompany.com
autoscaling:
enabled: true
minReplicas: 3
maxReplicas: 20
# Deploy to each environment
helm install my-app ./my-app/ \
--values config/values-base.yaml \
--values config/values-prod.yaml \
--namespace production \
--create-namespace
Packaging and Sharing a Chart
# Package the chart into a .tgz archive
helm package my-app/
# Creates: my-app-0.1.0.tgz
# Install from archive
helm install my-release ./my-app-0.1.0.tgz
# Create a simple file-based chart repository
mkdir -p helm-repo/charts
mv my-app-0.1.0.tgz helm-repo/charts/
helm repo index helm-repo/ # Creates index.yaml
# Host with any static file server (GitHub Pages, S3, etc.)
# Users add it with:
# helm repo add my-team https://my-bucket.s3.amazonaws.com/helm-repo/
Try It
# Create and validate a minimal chart
helm create webapp
# Lint it
helm lint webapp/
# Preview the default nginx install
helm template my-webapp webapp/ | grep "kind:" | sort -u
# Update the image to a different one
sed -i 's|repository: nginx|repository: nginxdemo/hello|' webapp/values.yaml
sed -i 's|tag: ""|tag: "plain-text"|' webapp/values.yaml
# Install it
helm install my-webapp ./webapp/ \
--namespace helm-create-demo \
--create-namespace \
--set service.type=NodePort
helm list -n helm-create-demo
kubectl get all -n helm-create-demo
# Cleanup
helm uninstall my-webapp -n helm-create-demo
kubectl delete namespace helm-create-demo
rm -rf webapp/
Key Takeaways
| # | Concept | One-liner |
|---|---|---|
| 1 | helm create scaffolds | Generates a complete working chart in seconds |
| 2 | helm lint catches errors | Run before every install or push to CI |
| 3 | helm template previews | See the final YAML before it touches the cluster |
| 4 | Multi-values files | Base + environment-specific overrides scale cleanly |
| 5 | helm package distributes | Creates a .tgz for sharing via a chart repo |
✅ Quick Check
Q1: helm lint shows no errors but the deployment fails after helm install. What could cause this?
Answer
`helm lint` validates chart structure and template syntax, but doesn't check Kubernetes API correctness or cluster-specific rules. The deployment could fail due to: image not existing in the registry, insufficient cluster permissions (RBAC), a node selector with no matching nodes, or an invalid resource reference. Use `helm template | kubectl apply --dry-run=server -f -` for deeper validation against a real cluster API.Q2: You have values-base.yaml with replicaCount: 2 and values-prod.yaml with replicaCount: 5. Which wins if you pass both with --values?
Answer
The last `--values` file wins. `helm install ... --values values-base.yaml --values values-prod.yaml` — `values-prod.yaml` overrides `values-base.yaml`. The replica count is `5`. This is the intentional merge behavior — base first, environment-specific last.Q3: Can you add custom top-level keys to values.yaml (like env: in the example above) that don’t appear in the upstream chart?