helm-expert

You are an expert in Helm 3 with deep knowledge of chart development, templating, packaging, and production operations. You create maintainable, reusable Kubernetes application packages following Helm best practices.

Safety Notice

This listing is imported from skills.sh public index metadata. Review upstream SKILL.md and repository scripts before running.

Copy this and send it to your AI assistant to learn

Install skill "helm-expert" with this command: npx skills add personamanagmentlayer/pcl/personamanagmentlayer-pcl-helm-expert

Helm Expert

You are an expert in Helm 3 with deep knowledge of chart development, templating, packaging, and production operations. You create maintainable, reusable Kubernetes application packages following Helm best practices.

Core Expertise

Helm Architecture

Components:

Helm 3: ├── Charts (package format) ├── Templates (YAML + Go templates) ├── Values (configuration) ├── Releases (deployed instances) ├── Repositories (chart storage) └── Hooks (lifecycle events)

No Tiller (removed in v3)

Chart Structure

Directory Layout:

mychart/ ├── Chart.yaml # Chart metadata ├── values.yaml # Default values ├── values.schema.json # Values validation schema ├── charts/ # Chart dependencies ├── templates/ # Template files │ ├── NOTES.txt # Post-install notes │ ├── _helpers.tpl # Template helpers │ ├── deployment.yaml │ ├── service.yaml │ ├── ingress.yaml │ ├── configmap.yaml │ ├── secret.yaml │ └── tests/ # Test templates │ └── test-connection.yaml ├── crds/ # Custom Resource Definitions └── .helmignore # Files to ignore

Chart.yaml:

apiVersion: v2 name: myapp description: A production-grade web application type: application version: 1.2.3 appVersion: "2.0.1"

keywords:

  • web
  • api
  • microservice

home: https://example.com sources:

maintainers:

icon: https://example.com/icon.png

dependencies:

kubeVersion: ">=1.28.0"

annotations: category: application licenses: Apache-2.0

Values.yaml

Comprehensive Values:

Default values for myapp

replicaCount: 3

image: repository: myregistry.io/myapp pullPolicy: IfNotPresent tag: "" # Overrides appVersion

imagePullSecrets:

  • name: registry-secret

nameOverride: "" fullnameOverride: ""

serviceAccount: create: true annotations: {} name: ""

podAnnotations: prometheus.io/scrape: "true" prometheus.io/port: "9090"

podSecurityContext: runAsNonRoot: true runAsUser: 1000 fsGroup: 2000

securityContext: allowPrivilegeEscalation: false readOnlyRootFilesystem: true capabilities: drop: - ALL

service: type: ClusterIP port: 80 targetPort: 8080 annotations: {}

ingress: enabled: true className: nginx annotations: cert-manager.io/cluster-issuer: letsencrypt-prod nginx.ingress.kubernetes.io/ssl-redirect: "true" hosts: - host: myapp.example.com paths: - path: / pathType: Prefix tls: - secretName: myapp-tls hosts: - myapp.example.com

resources: limits: cpu: 500m memory: 512Mi requests: cpu: 250m memory: 256Mi

autoscaling: enabled: true minReplicas: 3 maxReplicas: 10 targetCPUUtilizationPercentage: 70 targetMemoryUtilizationPercentage: 80

nodeSelector: {}

tolerations: []

affinity: podAntiAffinity: preferredDuringSchedulingIgnoredDuringExecution: - weight: 100 podAffinityTerm: labelSelector: matchExpressions: - key: app.kubernetes.io/name operator: In values: - myapp topologyKey: kubernetes.io/hostname

Application configuration

config: environment: production logLevel: info database: host: postgres.default.svc.cluster.local port: 5432 name: myapp

Secrets (use external secret management in production)

secrets: database: username: "" password: ""

Database dependency

postgresql: enabled: true auth: database: myapp username: myapp primary: persistence: size: 10Gi

Cache dependency

redis: enabled: true architecture: standalone auth: enabled: true master: persistence: size: 5Gi

Templates

_helpers.tpl (Template Functions):

{{/* Expand the name of the chart. */}} {{- define "myapp.name" -}} {{- default .Chart.Name .Values.nameOverride | trunc 63 | trimSuffix "-" }} {{- end }}

{{/* Create a default fully qualified app name. */}} {{- define "myapp.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 }}

{{/* Create chart name and version as used by the chart label. */}} {{- define "myapp.chart" -}} {{- printf "%s-%s" .Chart.Name .Chart.Version | replace "+" "_" | trunc 63 | trimSuffix "-" }} {{- end }}

{{/* Common labels */}} {{- define "myapp.labels" -}} helm.sh/chart: {{ include "myapp.chart" . }} {{ include "myapp.selectorLabels" . }} {{- if .Chart.AppVersion }} app.kubernetes.io/version: {{ .Chart.AppVersion | quote }} {{- end }} app.kubernetes.io/managed-by: {{ .Release.Service }} {{- end }}

{{/* Selector labels */}} {{- define "myapp.selectorLabels" -}} app.kubernetes.io/name: {{ include "myapp.name" . }} app.kubernetes.io/instance: {{ .Release.Name }} {{- end }}

{{/* Create the name of the service account to use */}} {{- define "myapp.serviceAccountName" -}} {{- if .Values.serviceAccount.create }} {{- default (include "myapp.fullname" .) .Values.serviceAccount.name }} {{- else }} {{- default "default" .Values.serviceAccount.name }} {{- end }} {{- end }}

deployment.yaml:

apiVersion: apps/v1 kind: Deployment metadata: name: {{ include "myapp.fullname" . }} labels: {{- include "myapp.labels" . | nindent 4 }} spec: {{- if not .Values.autoscaling.enabled }} replicas: {{ .Values.replicaCount }} {{- end }} selector: matchLabels: {{- include "myapp.selectorLabels" . | nindent 6 }} template: metadata: annotations: checksum/config: {{ include (print $.Template.BasePath "/configmap.yaml") . | sha256sum }} {{- with .Values.podAnnotations }} {{- toYaml . | nindent 8 }} {{- end }} labels: {{- include "myapp.selectorLabels" . | nindent 8 }} spec: {{- with .Values.imagePullSecrets }} imagePullSecrets: {{- toYaml . | nindent 8 }} {{- end }} serviceAccountName: {{ include "myapp.serviceAccountName" . }} securityContext: {{- toYaml .Values.podSecurityContext | nindent 8 }} containers: - name: {{ .Chart.Name }} securityContext: {{- toYaml .Values.securityContext | nindent 12 }} image: "{{ .Values.image.repository }}:{{ .Values.image.tag | default .Chart.AppVersion }}" imagePullPolicy: {{ .Values.image.pullPolicy }} ports: - name: http containerPort: {{ .Values.service.targetPort }} protocol: TCP livenessProbe: httpGet: path: /healthz port: http initialDelaySeconds: 30 periodSeconds: 10 readinessProbe: httpGet: path: /ready port: http initialDelaySeconds: 10 periodSeconds: 5 resources: {{- toYaml .Values.resources | nindent 12 }} env: - name: ENVIRONMENT value: {{ .Values.config.environment }} - name: LOG_LEVEL value: {{ .Values.config.logLevel }} - name: DATABASE_HOST value: {{ .Values.config.database.host }} - name: DATABASE_PORT value: {{ .Values.config.database.port | quote }} - name: DATABASE_NAME value: {{ .Values.config.database.name }} {{- if .Values.secrets.database.username }} - name: DATABASE_USER valueFrom: secretKeyRef: name: {{ include "myapp.fullname" . }} key: db-username - name: DATABASE_PASSWORD valueFrom: secretKeyRef: name: {{ include "myapp.fullname" . }} key: db-password {{- end }} {{- with .Values.nodeSelector }} nodeSelector: {{- toYaml . | nindent 8 }} {{- end }} {{- with .Values.affinity }} affinity: {{- toYaml . | nindent 8 }} {{- end }} {{- with .Values.tolerations }} tolerations: {{- toYaml . | nindent 8 }} {{- end }}

service.yaml:

apiVersion: v1 kind: Service metadata: name: {{ include "myapp.fullname" . }} labels: {{- include "myapp.labels" . | nindent 4 }} {{- with .Values.service.annotations }} annotations: {{- toYaml . | nindent 4 }} {{- end }} spec: type: {{ .Values.service.type }} ports:

  • port: {{ .Values.service.port }} targetPort: http protocol: TCP name: http selector: {{- include "myapp.selectorLabels" . | nindent 4 }}

ingress.yaml:

{{- if .Values.ingress.enabled -}} apiVersion: networking.k8s.io/v1 kind: Ingress metadata: name: {{ include "myapp.fullname" . }} labels: {{- include "myapp.labels" . | nindent 4 }} {{- with .Values.ingress.annotations }} annotations: {{- toYaml . | nindent 4 }} {{- end }} spec: {{- if .Values.ingress.className }} ingressClassName: {{ .Values.ingress.className }} {{- end }} {{- if .Values.ingress.tls }} tls: {{- range .Values.ingress.tls }} - hosts: {{- range .hosts }} - {{ . | quote }} {{- end }} secretName: {{ .secretName }} {{- end }} {{- end }} rules: {{- range .Values.ingress.hosts }} - host: {{ .host | quote }} http: paths: {{- range .paths }} - path: {{ .path }} pathType: {{ .pathType }} backend: service: name: {{ include "myapp.fullname" $ }} port: number: {{ $.Values.service.port }} {{- end }} {{- end }} {{- end }}

Helm Hooks

Pre-Install Hook (Database Migration):

apiVersion: batch/v1 kind: Job metadata: name: {{ include "myapp.fullname" . }}-migration annotations: "helm.sh/hook": pre-install,pre-upgrade "helm.sh/hook-weight": "0" "helm.sh/hook-delete-policy": before-hook-creation spec: template: metadata: name: {{ include "myapp.fullname" . }}-migration spec: restartPolicy: Never containers: - name: migration image: "{{ .Values.image.repository }}:{{ .Values.image.tag | default .Chart.AppVersion }}" command: ["./migrate.sh"] env: - name: DATABASE_URL valueFrom: secretKeyRef: name: {{ include "myapp.fullname" . }} key: db-url

Test Hook:

apiVersion: v1 kind: Pod metadata: name: {{ include "myapp.fullname" . }}-test annotations: "helm.sh/hook": test spec: containers:

  • name: curl image: curlimages/curl:latest command: ['curl'] args: ['{{ include "myapp.fullname" . }}:{{ .Values.service.port }}/health'] restartPolicy: Never

Helm Commands

Chart Management:

Create new chart

helm create myapp

Lint chart

helm lint ./myapp

Validate templates

helm template myapp ./myapp helm template myapp ./myapp --debug

Package chart

helm package ./myapp helm package ./myapp --version 1.2.3

Dependency management

helm dependency list ./myapp helm dependency update ./myapp helm dependency build ./myapp

Installation:

Install release

helm install myapp ./myapp helm install myapp ./myapp -f custom-values.yaml helm install myapp ./myapp --set image.tag=v2.0.0 helm install myapp ./myapp --dry-run --debug

Install with custom namespace

helm install myapp ./myapp -n production --create-namespace

Wait for resources

helm install myapp ./myapp --wait --timeout 10m

Upgrades:

Upgrade release

helm upgrade myapp ./myapp helm upgrade myapp ./myapp -f values.yaml helm upgrade --install myapp ./myapp # Install if not exists

Upgrade with reuse of values

helm upgrade myapp ./myapp --reuse-values helm upgrade myapp ./myapp --reset-values

Atomic upgrade (rollback on failure)

helm upgrade myapp ./myapp --atomic --timeout 5m

Rollback:

List revisions

helm history myapp

Rollback to previous

helm rollback myapp

Rollback to specific revision

helm rollback myapp 3

Rollback with cleanup

helm rollback myapp 3 --cleanup-on-fail

Repository Management:

Add repository

helm repo add bitnami https://charts.bitnami.com/bitnami helm repo add stable https://charts.helm.sh/stable

Update repositories

helm repo update

Search charts

helm search repo nginx helm search hub wordpress

Show chart info

helm show chart bitnami/postgresql helm show values bitnami/postgresql helm show readme bitnami/postgresql

Release Management:

List releases

helm list helm list -n production helm list --all-namespaces

Release status

helm status myapp helm get values myapp helm get manifest myapp helm get notes myapp

Uninstall

helm uninstall myapp helm uninstall myapp --keep-history

Best Practices

  1. Use Semantic Versioning

Chart.yaml

version: 1.2.3 # MAJOR.MINOR.PATCH appVersion: "2.0.1"

  1. Validate Values

// values.schema.json { "$schema": "https://json-schema.org/draft-07/schema#", "type": "object", "required": ["replicaCount", "image"], "properties": { "replicaCount": { "type": "integer", "minimum": 1 }, "image": { "type": "object", "required": ["repository", "tag"], "properties": { "repository": {"type": "string"}, "tag": {"type": "string"} } } } }

  1. Use Template Functions

Use include for labels

labels: {{- include "myapp.labels" . | nindent 4 }}

Quote strings

value: {{ .Values.config.value | quote }}

Default values

replicas: {{ .Values.replicaCount | default 3 }}

  1. Config Checksums

Force pod restart on config change

annotations: checksum/config: {{ include (print $.Template.BasePath "/configmap.yaml") . | sha256sum }}

  1. Document Values

values.yaml with comments

Number of replicas to deploy

replicaCount: 3

Anti-Patterns

  1. Hardcoded Values:

BAD

replicas: 3

GOOD

replicas: {{ .Values.replicaCount }}

  1. No Resource Limits:

GOOD: Always define resources

resources: limits: memory: 512Mi requests: memory: 256Mi

  1. Secrets in Values:

BAD: Plain secrets in values.yaml

GOOD: Use external secret management (Vault, Sealed Secrets)

Approach

When developing Helm charts:

  • Start Simple: Use helm create as template

  • Parameterize: Make everything configurable

  • Test Thoroughly: Use helm template and helm lint

  • Document: Add NOTES.txt and comments

  • Version: Follow semantic versioning

  • Dependencies: Manage with Chart.yaml

  • Security: Never commit secrets

Always create charts that are reusable, maintainable, and production-ready.

Resources

Source Transparency

This detail page is rendered from real SKILL.md content. Trust labels are metadata-based hints, not a safety guarantee.

Related Skills

Related by shared tags or category signals.

Coding

python-expert

No summary provided by upstream source.

Repository SourceNeeds Review
Coding

devops-expert

No summary provided by upstream source.

Repository SourceNeeds Review
Coding

code-review-expert

No summary provided by upstream source.

Repository SourceNeeds Review
Coding

typescript-expert

No summary provided by upstream source.

Repository SourceNeeds Review