Claude Code Plugins

Community-maintained marketplace

Feedback

Create and manage Helm charts for Todo application deployment

Install Skill

1Download skill
2Enable skills in Claude

Open claude.ai/settings/capabilities and find the "Skills" section

3Upload to Claude

Click "Upload skill" and select the downloaded ZIP file

Note: Please verify skill by going through its instructions before using it.

SKILL.md

name helm-charts-setup
description Create and manage Helm charts for Todo application deployment
allowed-tools Bash, Write, Read, Glob, Edit

Helm Charts Setup Skill

Quick Start

  1. Read Phase 4 Constitution - prompts/constitution-prompt-phase-4.md
  2. Create Helm chart structure - Use helm create or manual structure
  3. Write templates - Convert K8s manifests to Helm templates
  4. Create values files - Separate for dev, staging, production
  5. Test and install - Verify chart works on Minikube

Helm Chart Structure

helm/todo-app/
├── Chart.yaml
├── values.yaml
├── values-dev.yaml
├── values-staging.yaml
├── values-prod.yaml
├── .helmignore
├── templates/
│   ├── NOTES.txt
│   ├── _helpers.tpl
│   ├── deployment.yaml
│   ├── service.yaml
│   ├── ingress.yaml
│   ├── configmap.yaml
│   ├── secret.yaml
│   ├── hpa.yaml
│   └── serviceaccount.yaml
└── tests/
    └── test-connection.yaml

Chart.yaml

apiVersion: v2
name: todo-app
description: A Helm chart for Todo Chatbot - Evolution of Todo Phase 4
type: application
version: 1.0.0
appVersion: "1.0.0"
keywords:
  - todo
  - chatbot
  - nextjs
  - fastapi
  - mcp
  - kubernetes
maintainers:
  - name: Developer
    email: dev@example.com
home: https://github.com/username/todo-web-hackthon
icon: https://raw.githubusercontent.com/username/todo-web-hackthon/main/assets/logo.png
sources:
  - https://github.com/username/todo-web-hackthon

.helmignore

# Patterns to ignore when packaging
*.md
.git
.gitignore
tests/
*.tgz
node_modules/
__pycache__/
venv/
.env
.vscode/
.idea/

Values.yaml

# Default values for todo-app

# Global configuration
global:
  namespace: todo-app
  imagePullPolicy: IfNotPresent
  registry: docker.io

# Frontend configuration
frontend:
  enabled: true
  replicaCount: 2
  image:
    repository: username/todo-frontend
    tag: "latest"
    pullPolicy: IfNotPresent
  service:
    type: NodePort
    port: 80
    targetPort: 3000
  resources:
    requests:
      cpu: 100m
      memory: 128Mi
    limits:
      cpu: 500m
      memory: 256Mi
  autoscaling:
    enabled: false
    minReplicas: 2
    maxReplicas: 5
    targetCPUUtilizationPercentage: 70
  env:
    NODE_ENV: production
    NEXT_PUBLIC_API_URL: ""
    NEXT_PUBLIC_MCP_URL: ""

# Backend configuration
backend:
  enabled: true
  replicaCount: 2
  image:
    repository: username/todo-backend
    tag: "latest"
    pullPolicy: IfNotPresent
  service:
    type: ClusterIP
    port: 8000
  resources:
    requests:
      cpu: 200m
      memory: 256Mi
    limits:
      cpu: 1000m
      memory: 512Mi
  autoscaling:
    enabled: false
    minReplicas: 2
    maxReplicas: 5
    targetCPUUtilizationPercentage: 70
  env:
    DATABASE_URL: ""
    GEMINI_API_KEY: ""
    BETTER_AUTH_SECRET: ""
    MCP_SERVER_URL: ""

# MCP Server configuration
mcpServer:
  enabled: true
  replicaCount: 1
  image:
    repository: username/todo-mcp-server
    tag: "latest"
    pullPolicy: IfNotPresent
  service:
    type: ClusterIP
    port: 8001
  resources:
    requests:
      cpu: 100m
      memory: 64Mi
    limits:
      cpu: 300m
      memory: 128Mi
  env:
    GEMINI_API_KEY: ""

# Ingress configuration
ingress:
  enabled: true
  className: nginx
  annotations: {}
  hosts:
    - host: todo.local
      paths:
        - path: /
          pathType: Prefix
  tls: []

# Service Account
serviceAccount:
  create: true
  annotations: {}
  name: ""

# Dapr configuration (Phase 5)
dapr:
  enabled: false
  config: app-config

Values Files by Environment

values-dev.yaml (Minikube Local)

global:
  registry: ""  # Use local images

frontend:
  replicaCount: 1
  resources:
    requests:
      cpu: 50m
      memory: 64Mi
    limits:
      cpu: 250m
      memory: 128Mi

backend:
  replicaCount: 1
  resources:
    requests:
      cpu: 100m
      memory: 128Mi
    limits:
      cpu: 500m
      memory: 256Mi

mcpServer:
  replicaCount: 1

ingress:
  enabled: true
  hosts:
    - host: todo.local
      paths:
        - path: /
          pathType: Prefix

values-staging.yaml (Pre-production)

global:
  registry: docker.io

frontend:
  replicaCount: 2
  autoscaling:
    enabled: true

backend:
  replicaCount: 2
  autoscaling:
    enabled: true

mcpServer:
  replicaCount: 1

values-prod.yaml (Production)

global:
  registry: ghcr.io  # GitHub Container Registry

frontend:
  replicaCount: 3
  autoscaling:
    enabled: true
    minReplicas: 3
    maxReplicas: 10
    targetCPUUtilizationPercentage: 70

backend:
  replicaCount: 3
  autoscaling:
    enabled: true
    minReplicas: 3
    maxReplicas: 10
    targetCPUUtilizationPercentage: 70

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

Template Files

_helpers.tpl

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

{{- /*
Create a default fully qualified app name.
*/}}
{{- define "todo-app.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 "todo-app.chart" -}}
{{- printf "%s-%s" .Chart.Name .Chart.Version | replace "+" "_" | trunc 63 | trimSuffix "-" }}
{{- end }}

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

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

templates/deployment.yaml

{{- if .Values.frontend.enabled }}
---
apiVersion: apps/v1
kind: Deployment
metadata:
  name: {{ include "todo-app.fullname" . }}-frontend
  namespace: {{ .Release.Namespace }}
  labels:
    {{- include "todo-app.labels" . | nindent 4 }}
spec:
  replicas: {{ .Values.frontend.replicaCount }}
  selector:
    matchLabels:
      app: frontend
      {{- include "todo-app.selectorLabels" . | nindent 6 }}
  template:
    metadata:
      labels:
        app: frontend
        {{- include "todo-app.selectorLabels" . | nindent 8 }}
    spec:
      containers:
      - name: frontend
        image: "{{ .Values.global.registry }}/{{ .Values.frontend.image.repository }}:{{ .Values.frontend.image.tag }}"
        imagePullPolicy: {{ .Values.frontend.image.pullPolicy }}
        ports:
        - name: http
          containerPort: {{ .Values.frontend.service.targetPort }}
          protocol: TCP
        env:
        - name: NODE_ENV
          value: {{ .Values.frontend.env.NODE_ENV | quote }}
        {{- range $key, $value := .Values.frontend.env }}
        {{- if ne $key "NODE_ENV" }}
        - name: {{ $key }}
          value: {{ $value | quote }}
        {{- end }}
        {{- end }}
        resources:
          {{- toYaml .Values.frontend.resources | nindent 10 }}
        livenessProbe:
          httpGet:
            path: /
            port: {{ .Values.frontend.service.targetPort }}
          initialDelaySeconds: 30
          periodSeconds: 10
        readinessProbe:
          httpGet:
            path: /
            port: {{ .Values.frontend.service.targetPort }}
          initialDelaySeconds: 5
          periodSeconds: 5
{{- end }}

templates/service.yaml

{{- if .Values.frontend.enabled }}
---
apiVersion: v1
kind: Service
metadata:
  name: {{ include "todo-app.fullname" . }}-frontend
  namespace: {{ .Release.Namespace }}
  labels:
    {{- include "todo-app.labels" . | nindent 4 }}
spec:
  type: {{ .Values.frontend.service.type }}
  selector:
    app: frontend
    {{- include "todo-app.selectorLabels" . | nindent 4 }}
  ports:
  - name: http
    port: {{ .Values.frontend.service.port }}
    targetPort: {{ .Values.frontend.service.targetPort }}
    protocol: TCP
{{- end }}

templates/hpa.yaml

{{- if and .Values.frontend.enabled .Values.frontend.autoscaling.enabled }}
---
apiVersion: autoscaling/v2
kind: HorizontalPodAutoscaler
metadata:
  name: {{ include "todo-app.fullname" . }}-frontend
  namespace: {{ .Release.Namespace }}
  labels:
    {{- include "todo-app.labels" . | nindent 4 }}
spec:
  scaleTargetRef:
    apiVersion: apps/v1
    kind: Deployment
    name: {{ include "todo-app.fullname" . }}-frontend
  minReplicas: {{ .Values.frontend.autoscaling.minReplicas }}
  maxReplicas: {{ .Values.frontend.autoscaling.maxReplicas }}
  metrics:
  - type: Resource
    resource:
      name: cpu
      target:
        type: Utilization
        averageUtilization: {{ .Values.frontend.autoscaling.targetCPUUtilizationPercentage }}
{{- end }}

templates/configmap.yaml

apiVersion: v1
kind: ConfigMap
metadata:
  name: {{ include "todo-app.fullname" . }}-config
  namespace: {{ .Release.Namespace }}
  labels:
    {{- include "todo-app.labels" . | nindent 4 }}
data:
  MCP_SERVER_URL: "http://{{ include "todo-app.fullname" . }}-mcp-server:8001"
  {{- if .Values.frontend.enabled }}
  NEXT_PUBLIC_API_URL: "http://{{ include "todo-app.fullname" . }}-backend:8000"
  NEXT_PUBLIC_MCP_URL: "http://{{ include "todo-app.fullname" . }}-mcp-server:8001"
  {{- end }}

templates/secret.yaml

apiVersion: v1
kind: Secret
metadata:
  name: {{ include "todo-app.fullname" . }}-secrets
  namespace: {{ .Release.Namespace }}
  labels:
    {{- include "todo-app.labels" . | nindent 4 }}
type: Opaque
stringData:
  GEMINI_API_KEY: {{ .Values.backend.env.GEMINI_API_KEY | default "" | quote }}
  BETTER_AUTH_SECRET: {{ .Values.backend.env.BETTER_AUTH_SECRET | default "" | quote }}
  {{- if .Values.backend.env.DATABASE_URL }}
  DATABASE_URL: {{ .Values.backend.env.DATABASE_URL | quote }}
  {{- end }}

templates/ingress.yaml

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

templates/NOTES.txt

Thank you for installing {{ .Chart.Name }}!

Your release is named {{ .Release.Name }}.

To learn more about the release, try:

  $ helm status {{ .Release.Name }}

To get the application URL:

  {{- if .Values.ingress.enabled }}
  http://{{ (index .Values.ingress.hosts 0).host }}
  {{- else }}
  Run: kubectl port-forward svc/{{ include "todo-app.fullname" . }}-frontend 8080:80
  {{- end }}

To upgrade the release:

  $ helm upgrade {{ .Release.Name }} .

To rollback the release:

  $ helm rollback {{ .Release.Name }}

Helm Commands

# Create new chart
helm create todo-app

# Lint chart
helm lint helm/todo-app

# Package chart
helm package helm/todo-app

# Install chart
helm install todo-app ./helm/todo-app \
  --namespace todo-app \
  --create-namespace \
  -f helm/todo-app/values-dev.yaml

# Upgrade chart
helm upgrade todo-app ./helm/todo-app \
  -f helm/todo-app/values-prod.yaml

# Get release status
helm status todo-app

# Get release values
helm get values todo-app

# Get release history
helm history todo-app

# Rollback
helm rollback todo-app [REVISION]

# Uninstall
helm uninstall todo-app

# List all releases
helm list --all-namespaces

# Dry run (preview)
helm upgrade --dry-run --debug todo-app ./helm/todo-app

kubectl-ai Integration

# Using kubectl-ai with Helm
kubectl-ai "install todo-app helm chart to production namespace"
kubectl-ai "upgrade todo-app with new image tag v1.2.0"
kubectl-ai "scale frontend using helm"
kubectl-ai "analyze helm release for issues"
kubectl-ai "optimize helm values for cost reduction"

Verification Checklist

After creating Helm chart:

  • Chart.yaml has correct API version (v2)
  • All templates use proper helper functions
  • Values file has all configurable parameters
  • .helmignore excludes unnecessary files
  • NOTES.txt provides helpful information
  • Helm lint passes without warnings
  • Chart can be packaged successfully
  • Chart installs on Minikube
  • All services are accessible
  • Upgrades work without issues

Troubleshooting

Issue Cause Fix
Template fails Invalid Helm syntax Run helm lint to find errors
Values not applied Wrong values path Use -f flag with correct path
Release fails Missing secrets Create secrets separately or use --set-file
Can't upgrade Existing release conflict Use helm upgrade instead of install
Ingress not working Wrong annotations Verify ingress controller is installed

CI/CD Integration

GitHub Actions Example

- name: Build and push Docker images
  run: |
    docker build -t ghcr.io/${{ github.repository }}/frontend:${{ github.sha }} ./frontend
    docker build -t ghcr.io/${{ github.repository }}/backend:${{ github.sha }} ./backend
    docker build -t ghcr.io/${{ github.repository }}/mcp-server:${{ github.sha }} ./backend
    echo "${{ secrets.GITHUB_TOKEN }}" | docker login ghcr.io -u ${{ github.actor }} --password-stdin
    docker push ghcr.io/${{ github.repository }}/frontend:${{ github.sha }}
    docker push ghcr.io/${{ github.repository }}/backend:${{ github.sha }}
    docker push ghcr.io/${{ github.repository }}/mcp-server:${{ github.sha }}

- name: Deploy with Helm
  run: |
    helm upgrade --install todo-app ./helm/todo-app \
      --namespace todo-app \
      --create-namespace \
      --set global.registry=ghcr.io \
      --set frontend.image.tag=${{ github.sha }} \
      --set backend.image.tag=${{ github.sha }} \
      --set mcpServer.image.tag=${{ github.sha }} \
      -f helm/todo-app/values-prod.yaml

Next Steps

After Helm chart creation:

  1. Test on Minikube with values-dev.yaml
  2. Test on staging cluster with values-staging.yaml
  3. Prepare for production deployment with values-prod.yaml
  4. Add Dapr annotations for Phase 5

References