🎉 DevOps Interview Prep Bundle is live — 1000+ Q&A across 20 topicsGet it →
All Articles

Build an AI-Powered Helm Chart Generator with Claude API

Writing a Helm chart from scratch is tedious. Build a tool that takes a service description and generates a production-ready Helm chart with values.yaml, templates, and a test suite.

DevOpsBoys5 min read
Share:Tweet

A complete Helm chart has 8+ files. Writing them all from scratch for every new service is repetitive. Here's how to build an AI tool that does it in seconds.


What You'll Generate

my-service/
├── Chart.yaml
├── values.yaml
├── templates/
│   ├── deployment.yaml
│   ├── service.yaml
│   ├── ingress.yaml
│   ├── hpa.yaml
│   ├── serviceaccount.yaml
│   ├── configmap.yaml
│   └── _helpers.tpl
└── tests/
    └── test-connection.yaml

The Generator

python
# helm_chart_generator.py
import anthropic
import os
import sys
import json
import zipfile
from pathlib import Path
from dataclasses import dataclass
 
SYSTEM_PROMPT = """You are a Helm chart expert. Generate complete, production-ready Helm charts.
 
Rules:
1. Follow Helm chart best practices (helm.sh/docs)
2. Use proper Go template syntax — all templates must be valid
3. Add comprehensive values.yaml with sensible defaults
4. Include HPA by default (set autoscaling.enabled=false in values)
5. Include Ingress template (set ingress.enabled=false by default)
6. Use named templates in _helpers.tpl for labels and selectors
7. Add resource requests/limits in values.yaml with reasonable defaults
8. Add probes (readiness + liveness) configurable via values
9. ServiceAccount creation with automountServiceAccountToken: false
10. Security context (runAsNonRoot: true, readOnlyRootFilesystem where possible)
 
Output format: JSON object with filename as key, file content as value.
Example:
{
  "Chart.yaml": "apiVersion: v2\\nname: my-service\\n...",
  "values.yaml": "replicaCount: 1\\n...",
  "templates/deployment.yaml": "{{- if .Values...}}\\n..."
}"""
 
 
@dataclass
class ServiceConfig:
    name: str
    description: str
    port: int
    language: str
    has_database: bool = False
    has_cache: bool = False
    has_ingress: bool = True
    chart_version: str = "0.1.0"
    app_version: str = "1.0.0"
 
 
def generate_helm_chart(config: ServiceConfig) -> dict:
    client = anthropic.Anthropic(api_key=os.environ["ANTHROPIC_API_KEY"])
    
    prompt = f"""Generate a complete Helm chart for this service:
 
Name: {config.name}
Description: {config.description}
Port: {config.port}
Language/Runtime: {config.language}
Has database dependency: {config.has_database}
Has cache dependency: {config.has_cache}
Needs ingress: {config.has_ingress}
Chart version: {config.chart_version}
App version: {config.app_version}
 
Return ONLY valid JSON with file paths as keys and file contents as values.
Include: Chart.yaml, values.yaml, templates/_helpers.tpl, templates/deployment.yaml, 
templates/service.yaml, templates/serviceaccount.yaml, templates/hpa.yaml
{"templates/ingress.yaml" if config.has_ingress else ""}
templates/NOTES.txt, tests/test-connection.yaml"""
 
    message = client.messages.create(
        model="claude-sonnet-4-6",
        max_tokens=8192,
        system=SYSTEM_PROMPT,
        messages=[{"role": "user", "content": prompt}]
    )
    
    response_text = message.content[0].text
    
    # Extract JSON from response
    start = response_text.find('{')
    end = response_text.rfind('}') + 1
    json_str = response_text[start:end]
    
    return json.loads(json_str)
 
 
def write_chart_files(files: dict, output_dir: Path, chart_name: str):
    chart_dir = output_dir / chart_name
    
    for file_path, content in files.items():
        full_path = chart_dir / file_path
        full_path.parent.mkdir(parents=True, exist_ok=True)
        full_path.write_text(content)
        print(f"  Created: {full_path}")
    
    return chart_dir
 
 
def create_zip(chart_dir: Path) -> Path:
    zip_path = chart_dir.parent / f"{chart_dir.name}.tgz"
    with zipfile.ZipFile(zip_path, 'w', zipfile.ZIP_DEFLATED) as zf:
        for file_path in chart_dir.rglob('*'):
            if file_path.is_file():
                zf.write(file_path, file_path.relative_to(chart_dir.parent))
    return zip_path
 
 
def main():
    print("Helm Chart Generator")
    print("=" * 40)
    
    # Collect service details
    name = input("Service name (lowercase, hyphens): ").strip()
    description = input("Service description: ").strip()
    port = int(input("Container port [8080]: ").strip() or "8080")
    language = input("Language/runtime [python/nodejs/java/go]: ").strip() or "nodejs"
    has_db = input("Needs database connection? [y/N]: ").lower() == 'y'
    has_cache = input("Needs Redis/cache? [y/N]: ").lower() == 'y'
    
    config = ServiceConfig(
        name=name,
        description=description,
        port=port,
        language=language,
        has_database=has_db,
        has_cache=has_cache
    )
    
    print(f"\n⚙️  Generating Helm chart for '{name}'...")
    
    files = generate_helm_chart(config)
    
    print(f"\n📁 Writing {len(files)} files:")
    chart_dir = write_chart_files(files, Path("."), name)
    
    print(f"\n✅ Chart generated: ./{name}/")
    print(f"\nNext steps:")
    print(f"  helm lint ./{name}/")
    print(f"  helm template ./{name}/ --debug")
    print(f"  helm install {name} ./{name}/ --dry-run")
 
 
if __name__ == "__main__":
    main()

Example Output

For input: payment-api, Python, port 8000, with database:

Chart.yaml:

yaml
apiVersion: v2
name: payment-api
description: Payment processing API service
type: application
version: 0.1.0
appVersion: "1.0.0"

values.yaml:

yaml
replicaCount: 2
 
image:
  repository: payment-api
  pullPolicy: IfNotPresent
  tag: ""
 
serviceAccount:
  create: true
  automount: false
  name: ""
 
service:
  type: ClusterIP
  port: 80
  targetPort: 8000
 
ingress:
  enabled: false
  className: nginx
  hosts:
    - host: payment-api.local
      paths:
        - path: /
          pathType: Prefix
 
resources:
  requests:
    cpu: 100m
    memory: 128Mi
  limits:
    cpu: 500m
    memory: 512Mi
 
autoscaling:
  enabled: false
  minReplicas: 2
  maxReplicas: 10
  targetCPUUtilizationPercentage: 70
 
readinessProbe:
  httpGet:
    path: /health
    port: 8000
  initialDelaySeconds: 10
  periodSeconds: 5
 
livenessProbe:
  httpGet:
    path: /health
    port: 8000
  initialDelaySeconds: 30
  periodSeconds: 10
 
securityContext:
  runAsNonRoot: true
  runAsUser: 1000
 
env: []
 
database:
  host: ""
  port: 5432
  name: payments
  secretName: db-credentials
  secretKey: connection-string

templates/deployment.yaml:

yaml
apiVersion: apps/v1
kind: Deployment
metadata:
  name: {{ include "payment-api.fullname" . }}
  labels:
    {{- include "payment-api.labels" . | nindent 4 }}
spec:
  {{- if not .Values.autoscaling.enabled }}
  replicas: {{ .Values.replicaCount }}
  {{- end }}
  selector:
    matchLabels:
      {{- include "payment-api.selectorLabels" . | nindent 6 }}
  template:
    metadata:
      labels:
        {{- include "payment-api.selectorLabels" . | nindent 8 }}
    spec:
      serviceAccountName: {{ include "payment-api.serviceAccountName" . }}
      securityContext:
        {{- toYaml .Values.securityContext | nindent 8 }}
      containers:
        - name: {{ .Chart.Name }}
          image: "{{ .Values.image.repository }}:{{ .Values.image.tag | default .Chart.AppVersion }}"
          imagePullPolicy: {{ .Values.image.pullPolicy }}
          ports:
            - containerPort: {{ .Values.service.targetPort }}
          readinessProbe:
            {{- toYaml .Values.readinessProbe | nindent 12 }}
          livenessProbe:
            {{- toYaml .Values.livenessProbe | nindent 12 }}
          resources:
            {{- toYaml .Values.resources | nindent 12 }}
          {{- if .Values.database.host }}
          env:
            - name: DATABASE_URL
              valueFrom:
                secretKeyRef:
                  name: {{ .Values.database.secretName }}
                  key: {{ .Values.database.secretKey }}
          {{- end }}
          {{- with .Values.env }}
          env:
            {{- toYaml . | nindent 12 }}
          {{- end }}

Validate and Test the Generated Chart

bash
# Validate chart structure
helm lint ./payment-api/
 
# Render templates to check output
helm template payment-api ./payment-api/ \
  --set image.tag=v1.0.0 \
  --set ingress.enabled=true \
  --debug
 
# Install dry run
helm install payment-api ./payment-api/ \
  --namespace payments \
  --create-namespace \
  --dry-run
 
# If looks good, install
helm install payment-api ./payment-api/ \
  --namespace payments \
  --create-namespace \
  --set image.tag=v1.0.0

Add to CI Pipeline for New Services

yaml
# .github/workflows/scaffold-service.yml
name: Scaffold New Service
 
on:
  workflow_dispatch:
    inputs:
      service_name:
        description: 'Service name'
        required: true
      port:
        description: 'Container port'
        default: '8080'
 
jobs:
  scaffold:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - uses: actions/setup-python@v5
        with:
          python-version: '3.12'
      
      - run: pip install anthropic
      
      - name: Generate Helm Chart
        env:
          ANTHROPIC_API_KEY: ${{ secrets.ANTHROPIC_API_KEY }}
        run: |
          python helm_chart_generator.py \
            --name "${{ inputs.service_name }}" \
            --port "${{ inputs.port }}" \
            --non-interactive
      
      - name: Validate Chart
        run: helm lint ./${{ inputs.service_name }}/
      
      - name: Create PR with Generated Chart
        uses: peter-evans/create-pull-request@v6
        with:
          title: "chore: scaffold Helm chart for ${{ inputs.service_name }}"
          branch: "scaffold/${{ inputs.service_name }}"

Generated charts aren't perfect — always review before deploying to production. But they give you a 90% complete starting point that would take 30–45 minutes to write manually.

For Helm hands-on labs including chart development, templating, and testing, KodeKloud has Helm courses that cover everything from basic installs to advanced chart authoring.

🔧

Today I Fixed

Short real fixes from production — posted daily

Browse fixes
Newsletter

Stay ahead of the curve

Get the latest DevOps, Kubernetes, AWS, and AI/ML guides delivered straight to your inbox. No spam — just practical engineering content.

Related Articles

Build an AI Helm Values Validator with Claude API

Helm's schema validation catches type errors but misses logical mistakes — a memory limit lower than the request, a missing resource block, an image tag that's 'latest' in production. Build a smarter validator with Claude API.

D
4 min readRead

Comments