Skip to content
helm-expert logo

Helm Expert

helm-expert

Expert-level Helm 3 package management, chart development, templating, and production operations

SKILL.md

Full skill instructions

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:
  - https://github.com/​example/​myapp

maintainers:
  - name: DevOps Team
    email: [email protected]
    url: https://example.com/​team

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

dependencies:
  - name: postgresql
    version: "12.x.x"
    repository: "https://charts.bitnami.com/​bitnami"
    condition: postgresql.enabled

  - name: redis
    version: "17.x.x"
    repository: "https://charts.bitnami.com/​bitnami"
    condition: redis.enabled
    tags:
      - cache

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"

2. 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"}
      }
    }
  }
}

3. 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 }}

4. Config Checksums

# Force pod restart on config change
annotations:
  checksum/​config: {{ include (print $.Template.BasePath "/​configmap.yaml") . | sha256sum }}

5. 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 }}

2. No Resource Limits:

# GOOD: Always define resources
resources:
  limits:
    memory: 512Mi
  requests:
    memory: 256Mi

3. Secrets in Values:

# BAD: Plain secrets in values.yaml
# GOOD: Use external secret management (Vault, Sealed Secrets)

Approach

When developing Helm charts:

  1. Start Simple: Use helm create as template
  2. Parameterize: Make everything configurable
  3. Test Thoroughly: Use helm template and helm lint
  4. Document: Add NOTES.txt and comments
  5. Version: Follow semantic versioning
  6. Dependencies: Manage with Chart.yaml
  7. Security: Never commit secrets

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

Resources

More DevOps & CI/CD skills

Project scaffolding, deployment configuration, and CI/CD setup for Google ADK agents.

6K 472.6K
View

Set up tracing, logging, and monitoring for deployed ADK agents across Cloud Trace, BigQuery, and third-party platforms.

6K 472.6K
View

Enterprise Azure infrastructure architect generating Bicep or Terraform from workload descriptions.

1.5K 454.3K
View
azure-kubernetes logo
DevOps & CI/CD

azure-kubernetes

Plan and configure production-ready Azure Kubernetes Service clusters with Day-0 and Day-1 best practices.

1.5K 447.1K
View

Raw mechanical interfaces fusing Swiss typographic print with military terminal aesthetics. Rigid grids, extreme type scale contrast, utilitarian color, analog degradation effects. For data-heavy dashboards, portfolios, or editorial sites that need to feel like declassified blueprints.

92.7K 354.9K
View
just-scrape logo
DevOps & CI/CD

just-scrape

Web search, scraping, extraction, crawling, and monitoring via ScrapeGraph AI CLI.

69 245K
View

Skill for working with Firebase Hosting (Classic). Use this when you want to deploy static web apps, Single Page Apps (SPAs), or simple microservices. Do NOT use for Firebase App Hosting.

462 159.7K
View

Deploy and manage web apps with Firebase App Hosting. Use this skill when deploying Next.js/Angular apps with backends.

462 159.1K
View
deploy-to-vercel logo
DevOps & CI/CD

deploy-to-vercel

Deploy applications and websites to Vercel. Use when the user requests deployment actions like "deploy my app", "deploy and give me the link", "push this live", or "create a preview deployment".

31.9K 146.6K
View
programmatic-seo logo
DevOps & CI/CD

programmatic-seo

Build SEO-optimized pages at scale using templates, data, and proven playbook patterns.

53.3K 140.1K
View

Design and build isolated, reusable Convex backend components with clear boundaries and app-facing wrappers.

63 123.5K
View

Deploy and manage projects on Vercel using token-based authentication. Use when working with Vercel CLI using access tokens rather than interactive login — e.g. "deploy to vercel", "set up vercel", "add environment variables to vercel".

31.9K 116.1K
View

Coding & apps AI tools

Opus Clip logo
Coding & apps

Opus Clip

Opus.ai: Revolutionize Your Web Experience

Free
View
I
Coding & apps

Imagica

Build a no-code AI app in minutes.

Freemium
View
E
Coding & apps

Emergent.sh

An IDE for code migration from legacy to modern frameworks through coding agents.

Freemium
View
Wonder Dynamics logo
Coding & apps

Wonder Dynamics

Automate CGI animation in live-action scenes

Paid
View
M
Coding & apps

Mixo

Launch a website in seconds with AI.

Paid
View
AI Code Convert logo
Coding & apps

AI Code Convert

Streamline Your Coding Experience with AI Code Helper

Free
View