Claude-skill-registry helm-chart-creation
install
source · Clone the upstream repo
git clone https://github.com/majiayu000/claude-skill-registry
Claude Code · Install into ~/.claude/skills/
T=$(mktemp -d) && git clone --depth=1 https://github.com/majiayu000/claude-skill-registry "$T" && mkdir -p ~/.claude/skills && cp -r "$T/skills/data/helm-chart-creation" ~/.claude/skills/majiayu000-claude-skill-registry-helm-chart-creation && rm -rf "$T"
manifest:
skills/data/helm-chart-creation/SKILL.mdsource content
Helm Chart Creation Skill
When to use this Skill
Use this Skill whenever you are:
- Creating new Helm charts for Kubernetes applications.
- Packaging multiple Kubernetes manifests into a reusable chart.
- Templating Kubernetes resources for different environments.
- Managing chart dependencies and subcharts.
- Deploying applications with Helm install/upgrade/rollback.
- Setting up Helm repositories for chart distribution.
This Skill works for any Helm project, not just a single repository.
Core Goals
- Create reusable, maintainable Helm charts.
- Follow official Helm best practices.
- Use proper templating for flexibility.
- Implement sensible defaults with override capability.
- Enable multi-environment deployments.
- Provide clear documentation for chart users.
What is Helm?
Helm is the package manager for Kubernetes. It allows you to:
- Package multiple K8s manifests into a single chart.
- Template values for different environments.
- Install/upgrade/rollback applications with single commands.
- Share charts via repositories.
Without Helm: With Helm: kubectl apply -f file1.yaml helm install my-app ./chart kubectl apply -f file2.yaml (one command!) kubectl apply -f file3.yaml ... (10+ commands)
Helm Chart Structure
Basic Structure
my-chart/ ├── Chart.yaml # Chart metadata (name, version) ├── values.yaml # Default configuration values ├── charts/ # Dependencies (subcharts) ├── templates/ # Kubernetes manifest templates │ ├── _helpers.tpl # Template helpers/partials │ ├── deployment.yaml │ ├── service.yaml │ ├── configmap.yaml │ ├── secret.yaml │ └── NOTES.txt # Post-install notes └── .helmignore # Files to ignore when packaging
Complete Structure
my-chart/ ├── Chart.yaml # Required: Chart metadata ├── Chart.lock # Generated: Dependency lock file ├── values.yaml # Required: Default values ├── values.schema.json # Optional: JSON schema for values ├── charts/ # Optional: Dependencies ├── crds/ # Optional: Custom Resource Definitions ├── templates/ # Required: Template files │ ├── _helpers.tpl # Partial templates │ ├── deployment.yaml │ ├── service.yaml │ ├── configmap.yaml │ ├── secret.yaml │ ├── ingress.yaml │ ├── hpa.yaml │ ├── serviceaccount.yaml │ ├── NOTES.txt # Post-install instructions │ └── tests/ # Helm tests │ └── test-connection.yaml ├── .helmignore # Ignore patterns └── README.md # Chart documentation
Chart.yaml
The Chart.yaml file contains metadata about the chart.
Minimal Chart.yaml
apiVersion: v2 name: my-app description: A Helm chart for my application type: application version: 0.1.0 appVersion: "1.0.0"
Complete Chart.yaml
apiVersion: v2 name: todo-app description: A Helm chart for Todo Application type: application version: 1.0.0 appVersion: "1.0.0" # Chart maintainers maintainers: - name: Your Name email: your@email.com url: https://github.com/yourusername # Keywords for searching keywords: - todo - fastapi - nextjs # Home page home: https://github.com/yourusername/todo-app # Source code sources: - https://github.com/yourusername/todo-app # Icon URL icon: https://example.com/icon.png # Dependencies (subcharts) dependencies: - name: postgresql version: "12.0.0" repository: "https://charts.bitnami.com/bitnami" condition: postgresql.enabled # Kubernetes version constraint kubeVersion: ">=1.25.0"
Chart Types
| Type | Description |
|---|---|
| Deploys an application (default) |
| Provides helpers for other charts |
Versioning
- version: Chart version (SemVer 2)
- appVersion: Application version (informational)
version: 1.2.3 # Chart version appVersion: "2.0.0" # Your app's version
values.yaml
The values.yaml file contains default configuration values.
Basic values.yaml
# Number of replicas replicaCount: 1 # Container image image: repository: my-app tag: "latest" pullPolicy: IfNotPresent # Service configuration service: type: ClusterIP port: 80 # Resource limits resources: limits: cpu: 500m memory: 512Mi requests: cpu: 100m memory: 128Mi
Complete values.yaml
# ====================== # Global settings # ====================== global: environment: production # ====================== # Backend Configuration # ====================== backend: enabled: true replicaCount: 2 image: repository: todo-backend tag: "v1.0.0" pullPolicy: IfNotPresent service: type: ClusterIP port: 8000 resources: requests: cpu: "100m" memory: "128Mi" limits: cpu: "500m" memory: "512Mi" # Environment variables env: APP_ENV: "production" LOG_LEVEL: "info" # Health probes livenessProbe: enabled: true path: /health initialDelaySeconds: 10 periodSeconds: 15 readinessProbe: enabled: true path: /ready initialDelaySeconds: 5 periodSeconds: 10 # ====================== # Frontend Configuration # ====================== frontend: enabled: true replicaCount: 2 image: repository: todo-frontend tag: "v1.0.0" pullPolicy: IfNotPresent service: type: NodePort port: 3000 nodePort: 30000 resources: requests: cpu: "100m" memory: "128Mi" limits: cpu: "500m" memory: "256Mi" env: NEXT_PUBLIC_API_URL: "http://backend-service:8000" # ====================== # Secrets (use external secret manager in production) # ====================== secrets: databaseUrl: "" authSecret: "" apiKey: "" # ====================== # Ingress Configuration # ====================== ingress: enabled: false className: "nginx" hosts: - host: todo.example.com paths: - path: / pathType: Prefix tls: [] # ====================== # Autoscaling # ====================== autoscaling: enabled: false minReplicas: 2 maxReplicas: 10 targetCPUUtilizationPercentage: 80
Values Best Practices
- Group related values under parent keys.
- Use comments to document each section.
- Provide sensible defaults that work out of the box.
- Use
flags for optional features.enabled - Keep secrets empty (override at install time).
Templates
Templates are Kubernetes manifests with Go templating.
Template Syntax Basics
# Access values {{ .Values.replicaCount }} # Access chart metadata {{ .Chart.Name }} {{ .Chart.Version }} # Access release info {{ .Release.Name }} {{ .Release.Namespace }} # Built-in objects {{ .Capabilities.KubeVersion }}
Common Template Patterns
Accessing Values
# Simple value replicas: {{ .Values.replicaCount }} # Nested value image: {{ .Values.image.repository }}:{{ .Values.image.tag }} # With default port: {{ .Values.service.port | default 80 }} # Quote strings env: {{ .Values.environment | quote }}
Conditionals (if/else)
{{- if .Values.ingress.enabled }} apiVersion: networking.k8s.io/v1 kind: Ingress metadata: name: {{ .Release.Name }}-ingress spec: # ... {{- end }} # if-else {{- if eq .Values.service.type "NodePort" }} nodePort: {{ .Values.service.nodePort }} {{- else }} # ClusterIP doesn't need nodePort {{- end }} # Multiple conditions {{- if and .Values.backend.enabled (gt .Values.backend.replicaCount 0) }} # Deploy backend {{- end }}
Loops (range)
# Loop over list env: {{- range .Values.env }} - name: {{ .name }} value: {{ .value | quote }} {{- end }} # Loop over map {{- range $key, $value := .Values.labels }} {{ $key }}: {{ $value | quote }} {{- end }} # Loop with index {{- range $index, $host := .Values.ingress.hosts }} - host: {{ $host }} {{- end }}
With (Scope)
# Narrow scope for cleaner templates {{- with .Values.backend }} spec: replicas: {{ .replicaCount }} template: spec: containers: - name: backend image: {{ .image.repository }}:{{ .image.tag }} {{- end }}
_helpers.tpl
Define reusable template helpers in
templates/_helpers.tpl.
{{/* Expand the name of the chart. */}} {{- define "my-chart.name" -}} {{- default .Chart.Name .Values.nameOverride | trunc 63 | trimSuffix "-" }} {{- end }} {{/* Create a default fully qualified app name. */}} {{- define "my-chart.fullname" -}} {{- if .Values.fullnameOverride }} {{- .Values.fullnameOverride | trunc 63 | trimSuffix "-" }} {{- else }} {{- $name := default .Chart.Name .Values.nameOverride }} {{- if contains $name .Release.Name }} {{- .Release.Name | trunc 63 | trimSuffix "-" }} {{- else }} {{- printf "%s-%s" .Release.Name $name | trunc 63 | trimSuffix "-" }} {{- end }} {{- end }} {{- end }} {{/* Common labels */}} {{- define "my-chart.labels" -}} helm.sh/chart: {{ include "my-chart.chart" . }} {{ include "my-chart.selectorLabels" . }} app.kubernetes.io/version: {{ .Chart.AppVersion | quote }} app.kubernetes.io/managed-by: {{ .Release.Service }} {{- end }} {{/* Selector labels */}} {{- define "my-chart.selectorLabels" -}} app.kubernetes.io/name: {{ include "my-chart.name" . }} app.kubernetes.io/instance: {{ .Release.Name }} {{- end }} {{/* Create chart name and version */}} {{- define "my-chart.chart" -}} {{- printf "%s-%s" .Chart.Name .Chart.Version | replace "+" "_" | trunc 63 | trimSuffix "-" }} {{- end }} {{/* Backend image */}} {{- define "my-chart.backendImage" -}} {{- printf "%s:%s" .Values.backend.image.repository .Values.backend.image.tag }} {{- end }}
Using Helpers
apiVersion: apps/v1 kind: Deployment metadata: name: {{ include "my-chart.fullname" . }}-backend labels: {{- include "my-chart.labels" . | nindent 4 }} spec: selector: matchLabels: {{- include "my-chart.selectorLabels" . | nindent 6 }} component: backend
Template Examples
deployment.yaml
{{- if .Values.backend.enabled }} apiVersion: apps/v1 kind: Deployment metadata: name: {{ include "my-chart.fullname" . }}-backend namespace: {{ .Release.Namespace }} labels: {{- include "my-chart.labels" . | nindent 4 }} component: backend spec: replicas: {{ .Values.backend.replicaCount }} selector: matchLabels: {{- include "my-chart.selectorLabels" . | nindent 6 }} component: backend template: metadata: labels: {{- include "my-chart.selectorLabels" . | nindent 8 }} component: backend spec: containers: - name: backend image: "{{ .Values.backend.image.repository }}:{{ .Values.backend.image.tag }}" imagePullPolicy: {{ .Values.backend.image.pullPolicy }} ports: - containerPort: {{ .Values.backend.service.port }} protocol: TCP {{- if .Values.backend.env }} env: {{- range $key, $value := .Values.backend.env }} - name: {{ $key }} value: {{ $value | quote }} {{- end }} {{- end }} envFrom: - secretRef: name: {{ include "my-chart.fullname" . }}-secrets {{- with .Values.backend.resources }} resources: {{- toYaml . | nindent 12 }} {{- end }} {{- if .Values.backend.livenessProbe.enabled }} livenessProbe: httpGet: path: {{ .Values.backend.livenessProbe.path }} port: {{ .Values.backend.service.port }} initialDelaySeconds: {{ .Values.backend.livenessProbe.initialDelaySeconds }} periodSeconds: {{ .Values.backend.livenessProbe.periodSeconds }} {{- end }} {{- if .Values.backend.readinessProbe.enabled }} readinessProbe: httpGet: path: {{ .Values.backend.readinessProbe.path }} port: {{ .Values.backend.service.port }} initialDelaySeconds: {{ .Values.backend.readinessProbe.initialDelaySeconds }} periodSeconds: {{ .Values.backend.readinessProbe.periodSeconds }} {{- end }} {{- end }}
service.yaml
{{- if .Values.backend.enabled }} apiVersion: v1 kind: Service metadata: name: {{ include "my-chart.fullname" . }}-backend namespace: {{ .Release.Namespace }} labels: {{- include "my-chart.labels" . | nindent 4 }} component: backend spec: type: {{ .Values.backend.service.type }} ports: - port: {{ .Values.backend.service.port }} targetPort: {{ .Values.backend.service.port }} protocol: TCP name: http {{- if and (eq .Values.backend.service.type "NodePort") .Values.backend.service.nodePort }} nodePort: {{ .Values.backend.service.nodePort }} {{- end }} selector: {{- include "my-chart.selectorLabels" . | nindent 4 }} component: backend {{- end }}
secret.yaml
apiVersion: v1 kind: Secret metadata: name: {{ include "my-chart.fullname" . }}-secrets namespace: {{ .Release.Namespace }} labels: {{- include "my-chart.labels" . | nindent 4 }} type: Opaque stringData: {{- if .Values.secrets.databaseUrl }} DATABASE_URL: {{ .Values.secrets.databaseUrl | quote }} {{- end }} {{- if .Values.secrets.authSecret }} BETTER_AUTH_SECRET: {{ .Values.secrets.authSecret | quote }} {{- end }} {{- if .Values.secrets.apiKey }} API_KEY: {{ .Values.secrets.apiKey | quote }} {{- end }}
configmap.yaml
apiVersion: v1 kind: ConfigMap metadata: name: {{ include "my-chart.fullname" . }}-config namespace: {{ .Release.Namespace }} labels: {{- include "my-chart.labels" . | nindent 4 }} data: APP_ENV: {{ .Values.global.environment | quote }} {{- range $key, $value := .Values.backend.env }} {{ $key }}: {{ $value | quote }} {{- end }}
NOTES.txt
Thank you for installing {{ .Chart.Name }}! Your release is named: {{ .Release.Name }} To get the application URL, run: {{- if .Values.ingress.enabled }} {{- range $host := .Values.ingress.hosts }} http{{ if $.Values.ingress.tls }}s{{ end }}://{{ $host.host }} {{- end }} {{- else if contains "NodePort" .Values.frontend.service.type }} export NODE_PORT=$(kubectl get --namespace {{ .Release.Namespace }} -o jsonpath="{.spec.ports[0].nodePort}" services {{ include "my-chart.fullname" . }}-frontend) export NODE_IP=$(kubectl get nodes --namespace {{ .Release.Namespace }} -o jsonpath="{.items[0].status.addresses[0].address}") echo "Frontend: http://$NODE_IP:$NODE_PORT" {{- else if contains "ClusterIP" .Values.frontend.service.type }} kubectl port-forward --namespace {{ .Release.Namespace }} svc/{{ include "my-chart.fullname" . }}-frontend 3000:{{ .Values.frontend.service.port }} echo "Frontend: http://127.0.0.1:3000" {{- end }} Backend API is available internally at: http://{{ include "my-chart.fullname" . }}-backend:{{ .Values.backend.service.port }}
Helm Commands
Chart Development
# Create new chart helm create my-chart # Lint chart (check for errors) helm lint ./my-chart # Template locally (see rendered output) helm template my-release ./my-chart # Template with custom values helm template my-release ./my-chart -f custom-values.yaml # Dry run (validate against cluster) helm install my-release ./my-chart --dry-run --debug
Install & Upgrade
# Install chart helm install my-release ./my-chart # Install in namespace helm install my-release ./my-chart -n my-namespace --create-namespace # Install with custom values file helm install my-release ./my-chart -f production-values.yaml # Install with value overrides helm install my-release ./my-chart --set replicaCount=3 # Install with multiple value overrides helm install my-release ./my-chart \ --set backend.replicaCount=3 \ --set frontend.replicaCount=2 \ --set secrets.databaseUrl="postgresql://..." # Upgrade release helm upgrade my-release ./my-chart # Upgrade with values helm upgrade my-release ./my-chart -f new-values.yaml # Install or upgrade (idempotent) helm upgrade --install my-release ./my-chart
Management
# List releases helm list helm list -A # All namespaces # Get release status helm status my-release # Get release history helm history my-release # Rollback to previous revision helm rollback my-release # Rollback to specific revision helm rollback my-release 2 # Uninstall release helm uninstall my-release # Get values of deployed release helm get values my-release # Get all info about release helm get all my-release
Repositories
# Add repository helm repo add bitnami https://charts.bitnami.com/bitnami # Update repositories helm repo update # Search repository helm search repo nginx # Install from repository helm install my-nginx bitnami/nginx
Packaging
# Package chart helm package ./my-chart # Package with version helm package ./my-chart --version 1.0.0 # Create index file (for repo) helm repo index .
Multi-Environment Deployment
values-dev.yaml
backend: replicaCount: 1 image: tag: "dev" resources: requests: cpu: "50m" memory: "64Mi" limits: cpu: "200m" memory: "256Mi" frontend: replicaCount: 1 image: tag: "dev" secrets: databaseUrl: "postgresql://dev-db/todo"
values-prod.yaml
backend: replicaCount: 3 image: tag: "v1.0.0" resources: requests: cpu: "200m" memory: "256Mi" limits: cpu: "1000m" memory: "1Gi" frontend: replicaCount: 3 image: tag: "v1.0.0" ingress: enabled: true hosts: - host: todo.example.com autoscaling: enabled: true minReplicas: 3 maxReplicas: 10
Deployment Commands
# Development helm upgrade --install todo-dev ./todo-chart \ -f values-dev.yaml \ -n development --create-namespace # Production helm upgrade --install todo-prod ./todo-chart \ -f values-prod.yaml \ --set secrets.databaseUrl="$DATABASE_URL" \ --set secrets.authSecret="$AUTH_SECRET" \ -n production --create-namespace
Best Practices Summary
Chart Structure
- Use
to generate initial structure.helm create - Keep template file names descriptive (resource type in name).
- Use
for reusable template functions._helpers.tpl - Include
with post-install instructions.NOTES.txt
Values
- Group related values under parent keys.
- Provide sensible defaults.
- Use
flags for optional features.enabled - Document values with comments.
- Keep secrets empty in default values.
Templates
- Use helper functions for names and labels.
- Use
for proper YAML indentation.nindent - Quote strings with
.| quote - Use
to control whitespace.{{- }} - Use
to scope nested values.with
Security
- Never commit actual secrets in values files.
- Use external secret management in production.
- Set resource limits on all containers.
- Run containers as non-root when possible.
Deployment
- Use
for idempotent deploys.helm upgrade --install - Use separate values files per environment.
- Pass secrets via
or external secret manager.--set - Test with
before actual deployment.--dry-run