
Helm Generator
- 407 installs
- 286 repo stars
- Updated July 26, 2026
- akin-ozer/cc-devops-skills
helm-generator is an agent skill that scaffolds Helm charts with defaults for services, ingress, secrets, and values.yaml for developers packaging Kubernetes deployments for staging or production.
About
helm-generator is an agent skill from akin-ozer/cc-devops-skills that scaffolds production-ready Helm charts when developers need to package microservices for Kubernetes without hand-writing every template from scratch. The skill generates chart structure with sensible defaults for Deployments, Services, Ingress rules, Secret references, and environment-specific values files suited to staging and production promotion workflows. Developers reach for helm-generator when onboarding a new service to a cluster, standardizing Helm layout across a fleet, or replacing ad-hoc kubectl manifests with chart-based releases integrated into CI/CD. helm-generator focuses on chart scaffolding and Kubernetes packaging conventions rather than writing application business logic, making it practical during infra iteration when Helm is already the deployment standard. Invoke when asked to create a new Helm chart, add ingress and secrets templates, or structure values.yaml for multi-environment Kubernetes releases.
- Helm chart scaffolding
- values.yaml and templates
- Release naming and labels
- Ingress and service stubs
- Environment override patterns
Helm Generator by the numbers
- 407 all-time installs (skills.sh)
- Ranked #294 of 1,435 DevOps & CI/CD skills by installs in the Skillselion catalog
- Data as of Aug 5, 2026 (Skillselion catalog sync)
npx skills add https://github.com/akin-ozer/cc-devops-skills --skill helm-generatorAdd your badge
Show developers this skill is listed on Skillselion. Paste this into your README.
| Installs | 407 |
|---|---|
| repo stars | ★ 286 |
| Last updated | July 26, 2026 |
| Repository | akin-ozer/cc-devops-skills ↗ |
How do you scaffold a Helm chart for Kubernetes?
Scaffold Helm charts with sensible defaults for services, ingress, secrets, and values when packaging Kubernetes deployments for staging or production.
Who is it for?
Platform and backend developers standardizing Kubernetes deployments who need Helm chart scaffolding with ingress, secrets, and environment values.
Skip if: Teams deploying with raw kubectl manifests, Docker Compose only, or serverless platforms without a Kubernetes target cluster.
When should I use this skill?
User asks to generate a Helm chart, scaffold Kubernetes ingress and secrets templates, or create values.yaml for staging and production.
What you get
Helm chart directory with Chart.yaml, values.yaml, Deployment, Service, Ingress, and Secret templates ready for helm install or CI release.
- Helm chart directory
- values.yaml for multiple environments
- Ingress and Secret templates
Files
Helm Chart Generator
Overview
Generate production-ready Helm charts with deterministic scaffolding, standard helpers, reusable templates, and validation loops.
Official Documentation:
- Helm Docs - Main documentation
- Chart Best Practices - Official best practices guide
- Template Functions - Built-in functions
- Sprig Functions - Extended function library
When to Use This Skill
| Use helm-generator | Use OTHER skill |
|---|---|
| Create new Helm charts | helm-validator: Validate/lint existing charts |
| Generate Helm templates | k8s-yaml-generator: Raw K8s YAML (no Helm) |
| Convert K8s manifests to Helm | k8s-debug: Debug deployed resources |
| Implement CRDs in Helm | k8s-yaml-validator: Validate K8s manifests |
Trigger Phrases
Use this skill when prompts include phrases like:
- "create Helm chart"
- "scaffold Helm chart"
- "generate Helm templates"
- "convert manifests to Helm chart"
- "build chart with Deployment/Service/Ingress"
Execution Flow
Follow these stages in order. Do not skip required stages.
Stage 1: Gather Requirements (Required)
Collect:
- Scope: full chart, specific templates, or conversion from manifests
- Workload:
deployment,statefulset, ordaemonset - Image reference: repository, optional tag, or digest
- Ports: service port and container target port (separate values)
- Runtime settings: resources, probes, autoscaling, ingress, storage
- Security: service account, security contexts, optional RBAC/network policies
Use request_user_input when critical fields are missing.
If request_user_input is unavailable, ask in normal chat and continue with explicit assumptions.
| Missing Information | Question to Ask |
|---|---|
| Image repository/tag | "What container image should be used? (e.g., nginx:1.25)" |
| Service port | "What service port should be exposed?" |
| Container target port | "What container port should traffic be forwarded to?" |
| Resource limits | "What CPU/memory limits should be set? (e.g., 500m CPU, 512Mi memory)" |
| Probe endpoints | "What health check endpoints does the app expose? (e.g., /health, /ready)" |
| Scaling requirements | "Should autoscaling be enabled? If yes, min/max replicas and target CPU%?" |
| Workload type | "What workload type: Deployment, StatefulSet, or DaemonSet?" |
| Storage requirements | "Does the application need persistent storage? Size and access mode?" |
Do not silently assume critical settings.
Stage 2: Lookup CRD Documentation (Only if CRDs Are In Scope)
1. Try Context7 first:
mcp__context7__resolve-library-idmcp__context7__query-docs
2. Fallback chain if Context7 is unavailable or incomplete:
- Operator official docs (preferred)
- General web search
Also consult references/crd_patterns.md for example patterns.
Stage 3: Scaffold Chart Structure (Required)
Run:
bash scripts/generate_chart_structure.sh <chart-name> <output-directory> [options]Options:
--image <repo>- Supports repo-only, tagged image, registry ports, and digest refs--port <number>- Service port (default: 80)--target-port <number>- Container target port (default: 8080)--type <type>- Workload type: deployment, statefulset, daemonset (default: deployment)--with-templates- Generate resource templates (deployment.yaml, service.yaml, etc.)--with-ingress- Include ingress template--with-hpa- Include HPA template--force- Overwrite existing chart without prompting
Image parsing behavior:
--image nginx:1.27-> repositorynginx, tag1.27--image registry.local:5000/team/app-> repository kept intact--image ghcr.io/org/app@sha256:...-> digest mode (no tag concatenation)--tagcannot be combined with digest image references
Idempotency and overwrite behavior:
generate_chart_structure.sh: prompts before overwrite;--forceoverwrites non-interactively.generate_standard_helpers.sh: prompts before replacingtemplates/_helpers.tpl;--forcebypasses prompt.
Expected scaffold shape:
mychart/
Chart.yaml
values.yaml
templates/
_helpers.tpl
NOTES.txt
serviceaccount.yaml
service.yaml
configmap.yaml
secret.yaml
deployment.yaml|statefulset.yaml|daemonset.yaml
ingress.yaml (optional)
hpa.yaml (optional)
.helmignoreStage 4: Generate Standard Helpers
Run:
bash scripts/generate_standard_helpers.sh <chart-name> <chart-directory>Required helpers: name, fullname, chart, labels, selectorLabels, serviceAccountName.
Fallback:
- If script execution is blocked, copy
assets/_helpers-template.tpland replaceCHARTNAMEwith the chart name.
Stage 5: Consult References and Generate Templates (Required)
Consult relevant references once at this stage:
references/resource_templates.mdfor the resource patterns being generatedreferences/helm_template_functions.mdfor templating function usagereferences/crd_patterns.mdonly when CRDs are in scope
Example file-open commands:
sed -n '1,220p' references/resource_templates.md
sed -n '1,220p' references/helm_template_functions.mdResource coverage from references/resource_templates.md:
- Workloads: Deployment, StatefulSet, DaemonSet, Job, CronJob
- Services: Service, Ingress
- Config: ConfigMap, Secret
- RBAC: ServiceAccount, Role, RoleBinding, ClusterRole, ClusterRoleBinding
- Network: NetworkPolicy
- Autoscaling: HPA, PodDisruptionBudget
Required template patterns:
metadata:
name: {{ include "mychart.fullname" . }}
labels: {{- include "mychart.labels" . | nindent 4 }}
{{- with .Values.nodeSelector }}
nodeSelector: {{- toYaml . | nindent 2 }}
{{- end }}
annotations:
{{- if and .Values.configMap .Values.configMap.enabled }}
checksum/config: {{ include (print $.Template.BasePath "/configmap.yaml") . | sha256sum }}
{{- end }}Checksum annotations are required for workloads, but must be conditional and only reference generated templates (configmap.yaml, secret.yaml).
Stage 6: Create values.yaml
Structure guidelines:
- Group related settings logically
- Document every value with
# --comments - Provide sensible defaults
- Include security contexts, resource limits, probes
- Keep
service.portandservice.targetPortseparate and explicit - Keep
configMap.enabled/secret.enabledaligned with generated templates
See assets/values-schema-template.json for JSON Schema validation.
Stage 7: Validate
Preferred path: run the helm-validator skill.
If skill invocation is unavailable, run local commands directly:
helm lint <chart-dir>
helm template test <chart-dir>If helm is unavailable, report the block clearly and perform partial checks:
bash -n scripts/generate_chart_structure.shbash -n scripts/generate_standard_helpers.sh- Verify generated files and key fields manually
Re-run validation after any fixes.
Template Functions Quick Reference
See references/helm_template_functions.md for complete guide.
| Function | Purpose | Example |
|---|---|---|
required | Enforce required values | {{ required "msg" .Values.x }} |
default | Fallback value | `{{ .Values.x \ |
quote | Quote strings | `{{ .Values.x \ |
include | Use helpers | `{{ include "name" . \ |
toYaml | Convert to YAML | `{{ toYaml .Values.x \ |
tpl | Render as template | {{ tpl .Values.config . }} |
nindent | Newline + indent | `{{- include "x" . \ |
Working with CRDs
See references/crd_patterns.md for complete examples.
Key points:
- CRDs you ship ->
crds/directory (not templated, not deleted on uninstall) - CR instances ->
templates/directory (fully templated) - Always look up documentation for CRD spec requirements
- Document operator dependencies in Chart.yaml annotations
Converting Manifests to Helm
1. Parameterize: Names -> helpers, values -> values.yaml 2. Apply patterns: Labels, conditionals, toYaml for complex objects 3. Add helpers: Create _helpers.tpl with standard helpers 4. Validate: Run helm-validator (or local lint/template fallback), then test with different values
Error Handling
| Issue | Solution |
|---|---|
| Template syntax errors | helm template test <chart-dir> --debug --show-only templates/<file>.yaml |
| Undefined values | helm lint <chart-dir> --strict and add default/required |
| Checksum include errors | Ensure templates/configmap.yaml and templates/secret.yaml exist and configMap.enabled / secret.enabled are set correctly |
| Port mismatch (Service vs container) | Set both service.port and service.targetPort, then re-run helm template test <chart-dir> |
| CRD validation fails | Verify apiVersion/spec fields with Context7 or operator docs, then re-render |
| Script argument failures | Run bash scripts/generate_chart_structure.sh --help and pass required values for option flags |
Example Flows
Full scaffold with templates, ingress, HPA, and explicit port mapping:
bash scripts/generate_chart_structure.sh webapp ./charts \
--image ghcr.io/acme/webapp:2.3.1 \
--port 80 \
--target-port 8080 \
--type deployment \
--with-templates \
--with-ingress \
--with-hpaDigest-based image scaffold:
bash scripts/generate_chart_structure.sh api ./charts \
--image ghcr.io/acme/api@sha256:0123456789abcdef \
--with-templatesMinimal scaffold without templates:
bash scripts/generate_chart_structure.sh starter ./chartsScaffold Success Criteria
Mark complete only when all checks pass:
- [ ]
Chart.yaml,values.yaml,.helmignore,templates/NOTES.txt, andtemplates/_helpers.tplexist - [ ]
values.yamlcontains explicitservice.portandservice.targetPort - [ ] If
--with-templateswas used,serviceaccount.yaml,service.yaml,configmap.yaml,secret.yaml, and one workload template exist - [ ] Generated workload template uses conditional checksum annotations for config/secret
- [ ] Image rendering logic supports tag and digest modes
- [ ] Validation completed (
helm-validatorskill or local fallback commands) and outcomes reported
Resources
Scripts
| Script | Usage |
|---|---|
scripts/generate_chart_structure.sh | bash scripts/generate_chart_structure.sh <chart-name> <output-dir> [options] |
scripts/generate_standard_helpers.sh | bash scripts/generate_standard_helpers.sh <chart-name> <chart-dir> [--force] |
References
| File | Content |
|---|---|
references/helm_template_functions.md | Complete template function guide |
references/resource_templates.md | All K8s resource templates |
references/crd_patterns.md | CRD patterns (cert-manager, Prometheus, Istio, ArgoCD) |
Assets
| File | Purpose |
|---|---|
assets/_helpers-template.tpl | Standard helpers template |
assets/values-schema-template.json | JSON Schema for values validation |
Integration with helm-validator
After generating charts, invoke helm-validator and close the loop: 1. Generate chart/templates 2. Run helm-validator (or local fallback commands) 3. Fix identified issues 4. Re-validate until passing
rendered/
output/
# Test artifacts
test-charts/
*.tgz
{{/*
Expand the name of the chart.
*/}}
{{- define "CHARTNAME.name" -}}
{{- default .Chart.Name .Values.nameOverride | trunc 63 | trimSuffix "-" }}
{{- end }}
{{/*
Create a default fully qualified app name.
*/}}
{{- define "CHARTNAME.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 "CHARTNAME.chart" -}}
{{- printf "%s-%s" .Chart.Name .Chart.Version | replace "+" "_" | trunc 63 | trimSuffix "-" }}
{{- end }}
{{/*
Common labels
*/}}
{{- define "CHARTNAME.labels" -}}
helm.sh/chart: {{ include "CHARTNAME.chart" . }}
{{ include "CHARTNAME.selectorLabels" . }}
{{- if .Chart.AppVersion }}
app.kubernetes.io/version: {{ .Chart.AppVersion | quote }}
{{- end }}
app.kubernetes.io/managed-by: {{ .Release.Service }}
{{- end }}
{{/*
Selector labels
*/}}
{{- define "CHARTNAME.selectorLabels" -}}
app.kubernetes.io/name: {{ include "CHARTNAME.name" . }}
app.kubernetes.io/instance: {{ .Release.Name }}
{{- end }}
{{/*
Create the name of the service account to use
*/}}
{{- define "CHARTNAME.serviceAccountName" -}}
{{- if .Values.serviceAccount.create }}
{{- default (include "CHARTNAME.fullname" .) .Values.serviceAccount.name }}
{{- else }}
{{- default "default" .Values.serviceAccount.name }}
{{- end }}
{{- end }}
# Patterns to ignore when building packages.
# This supports shell glob matching, relative path matching, and
# negation (prefixed with !). Only one pattern per line.
.DS_Store
# Common VCS dirs
.git/
.gitignore
.bzr/
.bzrignore
.hg/
.hgignore
.svn/
# Common backup files
*.swp
*.bak
*.tmp
*.orig
*~
# Various IDEs
.project
.idea/
*.tmproj
.vscode/
# CI/CD
.github/
.gitlab-ci.yml
# Testing
test/
tests/
*.test
# Documentation
README.md
CONTRIBUTING.md{
"$schema": "https://json-schema.org/draft-07/schema#",
"title": "Values",
"type": "object",
"properties": {
"replicaCount": {
"description": "Number of replicas for the deployment",
"type": "integer",
"minimum": 0
},
"image": {
"description": "Image configuration",
"type": "object",
"required": ["repository"],
"properties": {
"repository": {
"description": "Container image repository",
"type": "string"
},
"pullPolicy": {
"description": "Image pull policy",
"type": "string",
"enum": ["Always", "IfNotPresent", "Never"]
},
"tag": {
"description": "Overrides the image tag (default is chart appVersion)",
"type": "string"
}
}
},
"imagePullSecrets": {
"description": "Image pull secrets for private registries",
"type": "array",
"items": {
"type": "object"
}
},
"nameOverride": {
"description": "Override the chart name",
"type": "string"
},
"fullnameOverride": {
"description": "Override the fullname",
"type": "string"
},
"serviceAccount": {
"description": "ServiceAccount configuration",
"type": "object",
"properties": {
"create": {
"description": "Specifies whether a service account should be created",
"type": "boolean"
},
"automount": {
"description": "Automatically mount service account token",
"type": "boolean"
},
"annotations": {
"description": "Annotations to add to the service account",
"type": "object"
},
"name": {
"description": "The name of the service account to use",
"type": "string"
}
}
},
"podAnnotations": {
"description": "Pod annotations",
"type": "object"
},
"podLabels": {
"description": "Pod labels",
"type": "object"
},
"podSecurityContext": {
"description": "Pod security context",
"type": "object"
},
"securityContext": {
"description": "Container security context",
"type": "object"
},
"service": {
"description": "Service configuration",
"type": "object",
"properties": {
"type": {
"description": "Service type",
"type": "string",
"enum": ["ClusterIP", "NodePort", "LoadBalancer", "ExternalName"]
},
"port": {
"description": "Service port",
"type": "integer",
"minimum": 1,
"maximum": 65535
},
"targetPort": {
"description": "Target container port",
"type": "integer",
"minimum": 1,
"maximum": 65535
}
}
},
"livenessProbe": {
"description": "Liveness probe configuration",
"type": "object",
"properties": {
"httpGet": {
"type": "object",
"properties": {
"path": {
"description": "Path for HTTP GET request",
"type": "string"
},
"port": {
"description": "Port for HTTP GET request",
"oneOf": [
{ "type": "string" },
{ "type": "integer", "minimum": 1, "maximum": 65535 }
]
}
}
},
"tcpSocket": {
"type": "object",
"properties": {
"port": {
"description": "Port for TCP socket check",
"oneOf": [
{ "type": "string" },
{ "type": "integer", "minimum": 1, "maximum": 65535 }
]
}
}
},
"exec": {
"type": "object",
"properties": {
"command": {
"description": "Command to execute",
"type": "array",
"items": { "type": "string" }
}
}
},
"initialDelaySeconds": {
"description": "Initial delay before starting probes",
"type": "integer",
"minimum": 0
},
"periodSeconds": {
"description": "Period between probes",
"type": "integer",
"minimum": 1
},
"timeoutSeconds": {
"description": "Timeout for probe",
"type": "integer",
"minimum": 1
},
"successThreshold": {
"description": "Minimum consecutive successes",
"type": "integer",
"minimum": 1
},
"failureThreshold": {
"description": "Minimum consecutive failures",
"type": "integer",
"minimum": 1
}
}
},
"readinessProbe": {
"description": "Readiness probe configuration",
"type": "object",
"properties": {
"httpGet": {
"type": "object",
"properties": {
"path": {
"description": "Path for HTTP GET request",
"type": "string"
},
"port": {
"description": "Port for HTTP GET request",
"oneOf": [
{ "type": "string" },
{ "type": "integer", "minimum": 1, "maximum": 65535 }
]
}
}
},
"tcpSocket": {
"type": "object",
"properties": {
"port": {
"description": "Port for TCP socket check",
"oneOf": [
{ "type": "string" },
{ "type": "integer", "minimum": 1, "maximum": 65535 }
]
}
}
},
"exec": {
"type": "object",
"properties": {
"command": {
"description": "Command to execute",
"type": "array",
"items": { "type": "string" }
}
}
},
"initialDelaySeconds": {
"description": "Initial delay before starting probes",
"type": "integer",
"minimum": 0
},
"periodSeconds": {
"description": "Period between probes",
"type": "integer",
"minimum": 1
},
"timeoutSeconds": {
"description": "Timeout for probe",
"type": "integer",
"minimum": 1
},
"successThreshold": {
"description": "Minimum consecutive successes",
"type": "integer",
"minimum": 1
},
"failureThreshold": {
"description": "Minimum consecutive failures",
"type": "integer",
"minimum": 1
}
}
},
"startupProbe": {
"description": "Startup probe configuration (for slow-starting containers)",
"type": "object",
"properties": {
"httpGet": {
"type": "object",
"properties": {
"path": {
"description": "Path for HTTP GET request",
"type": "string"
},
"port": {
"description": "Port for HTTP GET request",
"oneOf": [
{ "type": "string" },
{ "type": "integer", "minimum": 1, "maximum": 65535 }
]
}
}
},
"tcpSocket": {
"type": "object",
"properties": {
"port": {
"description": "Port for TCP socket check",
"oneOf": [
{ "type": "string" },
{ "type": "integer", "minimum": 1, "maximum": 65535 }
]
}
}
},
"exec": {
"type": "object",
"properties": {
"command": {
"description": "Command to execute",
"type": "array",
"items": { "type": "string" }
}
}
},
"initialDelaySeconds": {
"description": "Initial delay before starting probes",
"type": "integer",
"minimum": 0
},
"periodSeconds": {
"description": "Period between probes",
"type": "integer",
"minimum": 1
},
"timeoutSeconds": {
"description": "Timeout for probe",
"type": "integer",
"minimum": 1
},
"failureThreshold": {
"description": "Minimum consecutive failures before giving up",
"type": "integer",
"minimum": 1
}
}
},
"env": {
"description": "Environment variables",
"type": "array",
"items": {
"type": "object",
"required": ["name"],
"properties": {
"name": {
"description": "Environment variable name",
"type": "string"
},
"value": {
"description": "Environment variable value",
"type": "string"
},
"valueFrom": {
"description": "Source for environment variable value",
"type": "object",
"properties": {
"secretKeyRef": {
"type": "object",
"properties": {
"name": { "type": "string" },
"key": { "type": "string" }
}
},
"configMapKeyRef": {
"type": "object",
"properties": {
"name": { "type": "string" },
"key": { "type": "string" }
}
},
"fieldRef": {
"type": "object",
"properties": {
"fieldPath": { "type": "string" }
}
}
}
}
}
}
},
"envFrom": {
"description": "Environment variables from ConfigMaps or Secrets",
"type": "array",
"items": {
"type": "object",
"properties": {
"configMapRef": {
"type": "object",
"properties": {
"name": {
"description": "ConfigMap name",
"type": "string"
}
}
},
"secretRef": {
"type": "object",
"properties": {
"name": {
"description": "Secret name",
"type": "string"
}
}
},
"prefix": {
"description": "Prefix for environment variable names",
"type": "string"
}
}
}
},
"volumeMounts": {
"description": "Volume mounts for the container",
"type": "array",
"items": {
"type": "object",
"required": ["name", "mountPath"],
"properties": {
"name": {
"description": "Volume name (must match a volume)",
"type": "string"
},
"mountPath": {
"description": "Path within the container",
"type": "string"
},
"subPath": {
"description": "Sub-path within the volume",
"type": "string"
},
"readOnly": {
"description": "Mount as read-only",
"type": "boolean"
}
}
}
},
"volumes": {
"description": "Volumes for the pod",
"type": "array",
"items": {
"type": "object",
"required": ["name"],
"properties": {
"name": {
"description": "Volume name",
"type": "string"
},
"configMap": {
"type": "object",
"properties": {
"name": { "type": "string" },
"items": {
"type": "array",
"items": {
"type": "object",
"properties": {
"key": { "type": "string" },
"path": { "type": "string" }
}
}
}
}
},
"secret": {
"type": "object",
"properties": {
"secretName": { "type": "string" },
"items": {
"type": "array",
"items": {
"type": "object",
"properties": {
"key": { "type": "string" },
"path": { "type": "string" }
}
}
}
}
},
"emptyDir": {
"type": "object",
"properties": {
"medium": { "type": "string" },
"sizeLimit": { "type": "string" }
}
},
"persistentVolumeClaim": {
"type": "object",
"properties": {
"claimName": { "type": "string" },
"readOnly": { "type": "boolean" }
}
},
"hostPath": {
"type": "object",
"properties": {
"path": { "type": "string" },
"type": { "type": "string" }
}
}
}
}
},
"ingress": {
"description": "Ingress configuration",
"type": "object",
"properties": {
"enabled": {
"description": "Enable ingress",
"type": "boolean"
},
"className": {
"description": "Ingress class name",
"type": "string"
},
"annotations": {
"description": "Ingress annotations",
"type": "object"
},
"hosts": {
"description": "Ingress hosts configuration",
"type": "array",
"items": {
"type": "object",
"properties": {
"host": {
"type": "string"
},
"paths": {
"type": "array",
"items": {
"type": "object",
"properties": {
"path": {
"type": "string"
},
"pathType": {
"type": "string",
"enum": ["Exact", "Prefix", "ImplementationSpecific"]
}
}
}
}
}
}
},
"tls": {
"description": "Ingress TLS configuration",
"type": "array"
}
}
},
"resources": {
"description": "Resource limits and requests",
"type": "object",
"properties": {
"limits": {
"type": "object"
},
"requests": {
"type": "object"
}
}
},
"autoscaling": {
"description": "Horizontal Pod Autoscaler configuration",
"type": "object",
"properties": {
"enabled": {
"description": "Enable autoscaling",
"type": "boolean"
},
"minReplicas": {
"description": "Minimum number of replicas",
"type": "integer",
"minimum": 1
},
"maxReplicas": {
"description": "Maximum number of replicas",
"type": "integer",
"minimum": 1
},
"targetCPUUtilizationPercentage": {
"description": "Target CPU utilization percentage",
"type": "integer",
"minimum": 1,
"maximum": 100
},
"targetMemoryUtilizationPercentage": {
"description": "Target memory utilization percentage",
"type": "integer",
"minimum": 1,
"maximum": 100
}
}
},
"nodeSelector": {
"description": "Node selector",
"type": "object"
},
"tolerations": {
"description": "Tolerations",
"type": "array"
},
"affinity": {
"description": "Affinity rules",
"type": "object"
},
"configMap": {
"description": "ConfigMap template toggle and data, used by checksum annotations",
"type": "object",
"properties": {
"enabled": {
"description": "Enable ConfigMap generation and checksum annotation",
"type": "boolean"
},
"data": {
"description": "Key-value pairs to populate the ConfigMap",
"type": "object",
"additionalProperties": {
"type": "string"
}
}
}
},
"secret": {
"description": "Secret template toggle and data, used by checksum annotations",
"type": "object",
"properties": {
"enabled": {
"description": "Enable Secret generation and checksum annotation",
"type": "boolean"
},
"type": {
"description": "Kubernetes Secret type (e.g. Opaque, kubernetes.io/tls)",
"type": "string"
},
"stringData": {
"description": "Plain-text key-value pairs stored as Secret stringData",
"type": "object",
"additionalProperties": {
"type": "string"
}
}
}
}
}
}
# Patterns to ignore when building packages.
# This supports shell glob matching, relative path matching, and
# negation (prefixed with !). Only one pattern per line.
.DS_Store
# Common VCS dirs
.git/
.gitignore
.bzr/
.bzrignore
.hg/
.hgignore
.svn/
# Common backup files
*.swp
*.bak
*.tmp
*.orig
*~
# Various IDEs
.project
.idea/
*.tmproj
.vscode/
# CI/CD
.github/
.gitlab-ci.yml
# Testing
test/
tests/
*.test
# Documentation
README.md
CONTRIBUTING.mdapiVersion: v2
name: webapp
description: A production-ready Helm chart for deploying web applications
# Chart version (follows Semantic Versioning)
version: 0.1.0
# Application version being deployed
appVersion: "1.0.0"
type: application
# Minimum Kubernetes version required
kubeVersion: ">=1.24.0-0"
keywords:
- web
- application
- nginx
home: https://example.com
maintainers:
- name: DevOps Team
email: devops@example.com{{/*
Expand the name of the chart.
*/}}
{{- define "webapp.name" -}}
{{- default .Chart.Name .Values.nameOverride | trunc 63 | trimSuffix "-" }}
{{- end }}
{{/*
Create a default fully qualified app name.
We truncate at 63 chars because some Kubernetes name fields are limited to this (by the DNS naming spec).
If release name contains chart name it will be used as a full name.
*/}}
{{- define "webapp.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 "webapp.chart" -}}
{{- printf "%s-%s" .Chart.Name .Chart.Version | replace "+" "_" | trunc 63 | trimSuffix "-" }}
{{- end }}
{{/*
Common labels
*/}}
{{- define "webapp.labels" -}}
helm.sh/chart: {{ include "webapp.chart" . }}
{{ include "webapp.selectorLabels" . }}
{{- if .Chart.AppVersion }}
app.kubernetes.io/version: {{ .Chart.AppVersion | quote }}
{{- end }}
app.kubernetes.io/managed-by: {{ .Release.Service }}
{{- end }}
{{/*
Selector labels
*/}}
{{- define "webapp.selectorLabels" -}}
app.kubernetes.io/name: {{ include "webapp.name" . }}
app.kubernetes.io/instance: {{ .Release.Name }}
{{- end }}
{{/*
Create the name of the service account to use
*/}}
{{- define "webapp.serviceAccountName" -}}
{{- if .Values.serviceAccount.create }}
{{- default (include "webapp.fullname" .) .Values.serviceAccount.name }}
{{- else }}
{{- default "default" .Values.serviceAccount.name }}
{{- end }}
{{- end }}
{{/*
Return the proper image name
*/}}
{{- define "webapp.image" -}}
{{- $tag := .Values.image.tag | default .Chart.AppVersion }}
{{- printf "%s:%s" .Values.image.repository $tag }}
{{- end }}apiVersion: apps/v1
kind: Deployment
metadata:
name: {{ include "webapp.fullname" . }}
labels:
{{- include "webapp.labels" . | nindent 4 }}
spec:
{{- if not .Values.autoscaling.enabled }}
replicas: {{ .Values.replicaCount }}
{{- end }}
selector:
matchLabels:
{{- include "webapp.selectorLabels" . | nindent 6 }}
template:
metadata:
annotations:
{{- with .Values.podAnnotations }}
{{- toYaml . | nindent 8 }}
{{- end }}
{{- if .Values.configMap }}
{{- if .Values.configMap.enabled }}
checksum/config: {{ include (print $.Template.BasePath "/configmap.yaml") . | sha256sum }}
{{- end }}
{{- end }}
{{- if .Values.secret }}
{{- if .Values.secret.enabled }}
checksum/secret: {{ include (print $.Template.BasePath "/secret.yaml") . | sha256sum }}
{{- end }}
{{- end }}
labels:
{{- include "webapp.labels" . | nindent 8 }}
{{- with .Values.podLabels }}
{{- toYaml . | nindent 8 }}
{{- end }}
spec:
{{- with .Values.imagePullSecrets }}
imagePullSecrets:
{{- toYaml . | nindent 8 }}
{{- end }}
serviceAccountName: {{ include "webapp.serviceAccountName" . }}
securityContext:
{{- toYaml .Values.podSecurityContext | nindent 8 }}
containers:
- name: {{ .Chart.Name }}
securityContext:
{{- toYaml .Values.securityContext | nindent 12 }}
image: {{ include "webapp.image" . | quote }}
imagePullPolicy: {{ .Values.image.pullPolicy }}
ports:
- name: http
containerPort: {{ .Values.service.targetPort }}
protocol: TCP
{{- range .Values.extraPorts }}
- name: {{ .name }}
containerPort: {{ .containerPort }}
protocol: {{ .protocol | default "TCP" }}
{{- end }}
{{- with .Values.livenessProbe }}
livenessProbe:
{{- toYaml . | nindent 12 }}
{{- end }}
{{- with .Values.readinessProbe }}
readinessProbe:
{{- toYaml . | nindent 12 }}
{{- end }}
{{- with .Values.startupProbe }}
startupProbe:
{{- toYaml . | nindent 12 }}
{{- end }}
resources:
{{- toYaml .Values.resources | nindent 12 }}
{{- with .Values.env }}
env:
{{- toYaml . | nindent 12 }}
{{- end }}
{{- with .Values.envFrom }}
envFrom:
{{- toYaml . | nindent 12 }}
{{- end }}
{{- with .Values.volumeMounts }}
volumeMounts:
{{- toYaml . | nindent 12 }}
{{- end }}
{{- with .Values.volumes }}
volumes:
{{- toYaml . | nindent 8 }}
{{- 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 }}{{- if .Values.autoscaling.enabled }}
apiVersion: autoscaling/v2
kind: HorizontalPodAutoscaler
metadata:
name: {{ include "webapp.fullname" . }}
labels:
{{- include "webapp.labels" . | nindent 4 }}
spec:
scaleTargetRef:
apiVersion: apps/v1
kind: Deployment
name: {{ include "webapp.fullname" . }}
minReplicas: {{ .Values.autoscaling.minReplicas }}
maxReplicas: {{ .Values.autoscaling.maxReplicas }}
metrics:
{{- if .Values.autoscaling.targetCPUUtilizationPercentage }}
- type: Resource
resource:
name: cpu
target:
type: Utilization
averageUtilization: {{ .Values.autoscaling.targetCPUUtilizationPercentage }}
{{- end }}
{{- if .Values.autoscaling.targetMemoryUtilizationPercentage }}
- type: Resource
resource:
name: memory
target:
type: Utilization
averageUtilization: {{ .Values.autoscaling.targetMemoryUtilizationPercentage }}
{{- end }}
{{- end }}{{- if .Values.ingress.enabled -}}
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: {{ include "webapp.fullname" . }}
labels:
{{- include "webapp.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 "webapp.fullname" $ }}
port:
number: {{ $.Values.service.port }}
{{- end }}
{{- end }}
{{- end }}Thank you for installing {{ .Chart.Name }}!
Your release is named: {{ .Release.Name }}
Chart version: {{ .Chart.Version }}
App version: {{ .Chart.AppVersion }}
1. Get the application URL by running these commands:
{{- if .Values.ingress.enabled }}
{{- range $host := .Values.ingress.hosts }}
{{- range .paths }}
http{{ if $.Values.ingress.tls }}s{{ end }}://{{ $host.host }}{{ .path }}
{{- end }}
{{- end }}
{{- else if contains "NodePort" .Values.service.type }}
export NODE_PORT=$(kubectl get --namespace {{ .Release.Namespace }} -o jsonpath="{.spec.ports[0].nodePort}" services {{ include "webapp.fullname" . }})
export NODE_IP=$(kubectl get nodes --namespace {{ .Release.Namespace }} -o jsonpath="{.items[0].status.addresses[0].address}")
echo http://$NODE_IP:$NODE_PORT
{{- else if contains "LoadBalancer" .Values.service.type }}
NOTE: It may take a few minutes for the LoadBalancer IP to be available.
You can watch the status by running:
kubectl get --namespace {{ .Release.Namespace }} svc -w {{ include "webapp.fullname" . }}
export SERVICE_IP=$(kubectl get svc --namespace {{ .Release.Namespace }} {{ include "webapp.fullname" . }} --template "{{"{{ range (index .status.loadBalancer.ingress 0) }}{{.}}{{ end }}"}}")
echo http://$SERVICE_IP:{{ .Values.service.port }}
{{- else if contains "ClusterIP" .Values.service.type }}
export POD_NAME=$(kubectl get pods --namespace {{ .Release.Namespace }} -l "app.kubernetes.io/name={{ include "webapp.name" . }},app.kubernetes.io/instance={{ .Release.Name }}" -o jsonpath="{.items[0].metadata.name}")
export CONTAINER_PORT=$(kubectl get pod --namespace {{ .Release.Namespace }} $POD_NAME -o jsonpath="{.spec.containers[0].ports[0].containerPort}")
echo "Visit http://127.0.0.1:8080 to use your application"
kubectl --namespace {{ .Release.Namespace }} port-forward $POD_NAME 8080:$CONTAINER_PORT
{{- end }}
2. Check pod status:
kubectl get pods --namespace {{ .Release.Namespace }} -l "app.kubernetes.io/name={{ include "webapp.name" . }},app.kubernetes.io/instance={{ .Release.Name }}"
3. View logs:
kubectl logs --namespace {{ .Release.Namespace }} -l "app.kubernetes.io/name={{ include "webapp.name" . }},app.kubernetes.io/instance={{ .Release.Name }}"apiVersion: v1
kind: Service
metadata:
name: {{ include "webapp.fullname" . }}
labels:
{{- include "webapp.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 "webapp.selectorLabels" . | nindent 4 }}{{- if .Values.serviceAccount.create -}}
apiVersion: v1
kind: ServiceAccount
metadata:
name: {{ include "webapp.serviceAccountName" . }}
labels:
{{- include "webapp.labels" . | nindent 4 }}
{{- with .Values.serviceAccount.annotations }}
annotations:
{{- toYaml . | nindent 4 }}
{{- end }}
automountServiceAccountToken: {{ .Values.serviceAccount.automount }}
{{- end }}# Default values for webapp.
# This is a YAML-formatted file.
# Declare variables to be passed into your templates.
# -- Number of replicas for the deployment
replicaCount: 1
# -- Image configuration
image:
# -- Container image registry and repository
repository: nginx
# -- Image pull policy
pullPolicy: IfNotPresent
# -- Overrides the image tag (default is chart appVersion)
tag: ""
# -- Image pull secrets for private registries
imagePullSecrets: []
# -- Override the chart name
nameOverride: ""
# -- Override the fullname
fullnameOverride: ""
# -- ServiceAccount configuration
serviceAccount:
# -- Specifies whether a service account should be created
create: true
# -- Automatically mount service account token
automount: true
# -- Annotations to add to the service account
annotations: {}
# -- The name of the service account to use (default: generated from fullname)
name: ""
# -- Pod annotations
podAnnotations: {}
# -- Pod labels
podLabels: {}
# -- Pod security context
podSecurityContext:
runAsNonRoot: true
runAsUser: 1000
fsGroup: 1000
# -- Container security context
securityContext:
allowPrivilegeEscalation: false
capabilities:
drop:
- ALL
readOnlyRootFilesystem: true
# -- Service configuration
service:
# -- Service type (ClusterIP, NodePort, LoadBalancer)
type: ClusterIP
# -- Service port
port: 80
# -- Target container port
targetPort: 8080
# -- Annotations to add to the service
annotations: {}
# -- Ingress configuration
ingress:
# -- Enable ingress
enabled: false
# -- Ingress class name
className: "nginx"
# -- Ingress annotations
annotations: {}
# cert-manager.io/cluster-issuer: letsencrypt-prod
# nginx.ingress.kubernetes.io/ssl-redirect: "true"
# -- Ingress hosts configuration
hosts:
- host: webapp.local
paths:
- path: /
pathType: Prefix
# -- Ingress TLS configuration
tls: []
# - secretName: webapp-tls
# hosts:
# - webapp.local
# -- Resource limits and requests
resources:
limits:
cpu: 100m
memory: 128Mi
requests:
cpu: 100m
memory: 128Mi
# -- Horizontal Pod Autoscaler configuration
autoscaling:
enabled: false
minReplicas: 1
maxReplicas: 10
targetCPUUtilizationPercentage: 80
# targetMemoryUtilizationPercentage: 80
# -- Liveness probe configuration
livenessProbe:
httpGet:
path: /healthz
port: http
initialDelaySeconds: 30
periodSeconds: 10
timeoutSeconds: 5
failureThreshold: 3
# -- Readiness probe configuration
readinessProbe:
httpGet:
path: /ready
port: http
initialDelaySeconds: 5
periodSeconds: 5
timeoutSeconds: 3
failureThreshold: 3
# -- Startup probe configuration (for slow-starting containers)
startupProbe: {}
# httpGet:
# path: /healthz
# port: http
# initialDelaySeconds: 10
# periodSeconds: 10
# timeoutSeconds: 5
# failureThreshold: 30
# -- Node selector
nodeSelector: {}
# -- Tolerations
tolerations: []
# -- Affinity rules
affinity: {}
# -- Environment variables
env: []
# - name: ENV_VAR_NAME
# value: "value"
# - name: SECRET_VALUE
# valueFrom:
# secretKeyRef:
# name: my-secret
# key: secret-key
# -- Environment variables from ConfigMaps or Secrets
envFrom: []
# - configMapRef:
# name: my-configmap
# - secretRef:
# name: my-secret
# -- Volume mounts
volumeMounts: []
# - name: config
# mountPath: /etc/config
# readOnly: true
# -- Volumes
volumes: []
# - name: config
# configMap:
# name: my-config
# -- Extra ports to expose on the container
extraPorts: []
# - name: metrics
# containerPort: 9090
# protocol: TCPCRD Patterns and Examples
Common Custom Resource Definition patterns and examples for Helm charts.
Table of Contents
- cert-manager
- Prometheus Operator
- Istio
- ArgoCD
- Sealed Secrets
- External Secrets Operator
- Gateway API
- KEDA
- VerticalPodAutoscaler (VPA)
---
cert-manager
Certificate Resource
File: templates/certificate.yaml
{{- if .Values.certificate.enabled }}
apiVersion: cert-manager.io/v1
kind: Certificate
metadata:
name: {{ include "mychart.fullname" . }}-tls
labels:
{{- include "mychart.labels" . | nindent 4 }}
spec:
secretName: {{ include "mychart.fullname" . }}-tls
issuerRef:
name: {{ .Values.certificate.issuer.name }}
kind: {{ .Values.certificate.issuer.kind | default "ClusterIssuer" }}
{{- with .Values.certificate.issuer.group }}
group: {{ . }}
{{- end }}
commonName: {{ .Values.certificate.commonName | default (first .Values.certificate.dnsNames) }}
dnsNames:
{{- range .Values.certificate.dnsNames }}
- {{ . | quote }}
{{- end }}
{{- with .Values.certificate.ipAddresses }}
ipAddresses:
{{- toYaml . | nindent 4 }}
{{- end }}
{{- with .Values.certificate.uris }}
uris:
{{- toYaml . | nindent 4 }}
{{- end }}
{{- with .Values.certificate.duration }}
duration: {{ . }}
{{- end }}
{{- with .Values.certificate.renewBefore }}
renewBefore: {{ . }}
{{- end }}
{{- with .Values.certificate.usages }}
usages:
{{- toYaml . | nindent 4 }}
{{- end }}
{{- with .Values.certificate.privateKey }}
privateKey:
{{- toYaml . | nindent 4 }}
{{- end }}
{{- with .Values.certificate.keystores }}
keystores:
{{- toYaml . | nindent 4 }}
{{- end }}
{{- end }}Corresponding values.yaml:
certificate:
enabled: false
issuer:
name: letsencrypt-prod
kind: ClusterIssuer
group: cert-manager.io
commonName: ""
dnsNames:
- example.com
- www.example.com
ipAddresses: []
uris: []
duration: 2160h # 90 days
renewBefore: 360h # 15 days
usages:
- digital signature
- key encipherment
privateKey:
algorithm: RSA
encoding: PKCS1
size: 2048
rotationPolicy: Never
keystores: {}
# keystores:
# jks:
# create: true
# passwordSecretRef:
# name: jks-password
# key: passwordClusterIssuer Resource
File: templates/clusterissuer.yaml
{{- if .Values.certManager.clusterIssuer.create }}
apiVersion: cert-manager.io/v1
kind: ClusterIssuer
metadata:
name: {{ .Values.certManager.clusterIssuer.name }}
labels:
{{- include "mychart.labels" . | nindent 4 }}
spec:
{{- if eq .Values.certManager.clusterIssuer.type "acme" }}
acme:
server: {{ .Values.certManager.clusterIssuer.acme.server }}
email: {{ required "certManager.clusterIssuer.acme.email is required!" .Values.certManager.clusterIssuer.acme.email }}
privateKeySecretRef:
name: {{ .Values.certManager.clusterIssuer.acme.privateKeySecretName }}
solvers:
{{- range .Values.certManager.clusterIssuer.acme.solvers }}
- {{ toYaml . | nindent 6 }}
{{- end }}
{{- else if eq .Values.certManager.clusterIssuer.type "ca" }}
ca:
secretName: {{ .Values.certManager.clusterIssuer.ca.secretName }}
{{- else if eq .Values.certManager.clusterIssuer.type "selfSigned" }}
selfSigned: {}
{{- end }}
{{- end }}Corresponding values.yaml:
certManager:
clusterIssuer:
create: false
name: letsencrypt-prod
type: acme # acme, ca, selfSigned, vault, venafi
acme:
server: https://acme-v02.api.letsencrypt.org/directory
email: admin@example.com
privateKeySecretName: letsencrypt-prod-account-key
solvers:
- http01:
ingress:
class: nginx
# - dns01:
# cloudflare:
# email: admin@example.com
# apiTokenSecretRef:
# name: cloudflare-api-token
# key: api-token
ca:
secretName: ca-key-pair---
Prometheus Operator
ServiceMonitor Resource
File: templates/servicemonitor.yaml
{{- if .Values.metrics.serviceMonitor.enabled }}
apiVersion: monitoring.coreos.com/v1
kind: ServiceMonitor
metadata:
name: {{ include "mychart.fullname" . }}
labels:
{{- include "mychart.labels" . | nindent 4 }}
{{- with .Values.metrics.serviceMonitor.labels }}
{{- toYaml . | nindent 4 }}
{{- end }}
{{- with .Values.metrics.serviceMonitor.annotations }}
annotations:
{{- toYaml . | nindent 4 }}
{{- end }}
spec:
selector:
matchLabels:
{{- include "mychart.selectorLabels" . | nindent 6 }}
endpoints:
- port: {{ .Values.metrics.serviceMonitor.port | default "metrics" }}
{{- with .Values.metrics.serviceMonitor.interval }}
interval: {{ . }}
{{- end }}
{{- with .Values.metrics.serviceMonitor.scrapeTimeout }}
scrapeTimeout: {{ . }}
{{- end }}
{{- with .Values.metrics.serviceMonitor.path }}
path: {{ . }}
{{- end }}
{{- with .Values.metrics.serviceMonitor.scheme }}
scheme: {{ . }}
{{- end }}
{{- with .Values.metrics.serviceMonitor.tlsConfig }}
tlsConfig:
{{- toYaml . | nindent 6 }}
{{- end }}
{{- with .Values.metrics.serviceMonitor.relabelings }}
relabelings:
{{- toYaml . | nindent 6 }}
{{- end }}
{{- with .Values.metrics.serviceMonitor.metricRelabelings }}
metricRelabelings:
{{- toYaml . | nindent 6 }}
{{- end }}
{{- with .Values.metrics.serviceMonitor.namespaceSelector }}
namespaceSelector:
{{- toYaml . | nindent 4 }}
{{- end }}
{{- end }}Corresponding values.yaml:
metrics:
enabled: true
port: 9090
serviceMonitor:
enabled: false
labels: {}
annotations: {}
port: metrics
interval: 30s
scrapeTimeout: 10s
path: /metrics
scheme: http
tlsConfig: {}
relabelings: []
metricRelabelings: []
namespaceSelector: {}PrometheusRule Resource
File: templates/prometheusrule.yaml
{{- if .Values.metrics.prometheusRule.enabled }}
apiVersion: monitoring.coreos.com/v1
kind: PrometheusRule
metadata:
name: {{ include "mychart.fullname" . }}
labels:
{{- include "mychart.labels" . | nindent 4 }}
{{- with .Values.metrics.prometheusRule.labels }}
{{- toYaml . | nindent 4 }}
{{- end }}
spec:
groups:
{{- range .Values.metrics.prometheusRule.groups }}
- name: {{ .name }}
{{- with .interval }}
interval: {{ . }}
{{- end }}
rules:
{{- range .rules }}
- alert: {{ .alert }}
expr: {{ .expr }}
{{- with .for }}
for: {{ . }}
{{- end }}
{{- with .labels }}
labels:
{{- toYaml . | nindent 8 }}
{{- end }}
annotations:
{{- range $key, $value := .annotations }}
{{ $key }}: {{ $value | quote }}
{{- end }}
{{- end }}
{{- end }}
{{- end }}Corresponding values.yaml:
metrics:
prometheusRule:
enabled: false
labels: {}
groups:
- name: mychart-alerts
interval: 30s
rules:
- alert: HighErrorRate
expr: rate(http_requests_total{status=~"5.."}[5m]) > 0.05
for: 10m
labels:
severity: warning
annotations:
summary: "High error rate detected"
description: "Error rate is {{ $value }} errors per second"
- alert: PodDown
expr: up{job="mychart"} == 0
for: 5m
labels:
severity: critical
annotations:
summary: "Pod is down"
description: "Pod {{ $labels.pod }} is down"---
Istio
VirtualService Resource
File: templates/virtualservice.yaml
{{- if .Values.istio.virtualService.enabled }}
apiVersion: networking.istio.io/v1beta1
kind: VirtualService
metadata:
name: {{ include "mychart.fullname" . }}
labels:
{{- include "mychart.labels" . | nindent 4 }}
spec:
hosts:
{{- range .Values.istio.virtualService.hosts }}
- {{ . | quote }}
{{- end }}
{{- with .Values.istio.virtualService.gateways }}
gateways:
{{- toYaml . | nindent 4 }}
{{- end }}
{{- with .Values.istio.virtualService.http }}
http:
{{- toYaml . | nindent 4 }}
{{- end }}
{{- with .Values.istio.virtualService.tcp }}
tcp:
{{- toYaml . | nindent 4 }}
{{- end }}
{{- with .Values.istio.virtualService.tls }}
tls:
{{- toYaml . | nindent 4 }}
{{- end }}
{{- end }}Corresponding values.yaml:
istio:
virtualService:
enabled: false
hosts:
- example.com
gateways:
- istio-system/gateway
http:
- match:
- uri:
prefix: /api
route:
- destination:
host: mychart-svc
port:
number: 80
weight: 90
- destination:
host: mychart-svc-canary
port:
number: 80
weight: 10
timeout: 30s
retries:
attempts: 3
perTryTimeout: 10s
tcp: []
tls: []Gateway Resource
File: templates/gateway.yaml
{{- if .Values.istio.gateway.enabled }}
apiVersion: networking.istio.io/v1beta1
kind: Gateway
metadata:
name: {{ include "mychart.fullname" . }}
labels:
{{- include "mychart.labels" . | nindent 4 }}
spec:
selector:
{{- toYaml .Values.istio.gateway.selector | nindent 4 }}
servers:
{{- range .Values.istio.gateway.servers }}
- port:
number: {{ .port.number }}
name: {{ .port.name }}
protocol: {{ .port.protocol }}
hosts:
{{- range .hosts }}
- {{ . | quote }}
{{- end }}
{{- with .tls }}
tls:
{{- toYaml . | nindent 6 }}
{{- end }}
{{- end }}
{{- end }}Corresponding values.yaml:
istio:
gateway:
enabled: false
selector:
istio: ingressgateway
servers:
- port:
number: 80
name: http
protocol: HTTP
hosts:
- example.com
- port:
number: 443
name: https
protocol: HTTPS
hosts:
- example.com
tls:
mode: SIMPLE
credentialName: example-com-tlsDestinationRule Resource
File: templates/destinationrule.yaml
{{- if .Values.istio.destinationRule.enabled }}
apiVersion: networking.istio.io/v1beta1
kind: DestinationRule
metadata:
name: {{ include "mychart.fullname" . }}
labels:
{{- include "mychart.labels" . | nindent 4 }}
spec:
host: {{ .Values.istio.destinationRule.host | default (include "mychart.fullname" .) }}
{{- with .Values.istio.destinationRule.trafficPolicy }}
trafficPolicy:
{{- toYaml . | nindent 4 }}
{{- end }}
{{- with .Values.istio.destinationRule.subsets }}
subsets:
{{- toYaml . | nindent 4 }}
{{- end }}
{{- end }}Corresponding values.yaml:
istio:
destinationRule:
enabled: false
host: mychart-svc
trafficPolicy:
connectionPool:
tcp:
maxConnections: 100
http:
http1MaxPendingRequests: 50
http2MaxRequests: 100
loadBalancer:
simple: LEAST_REQUEST
outlierDetection:
consecutiveErrors: 5
interval: 30s
baseEjectionTime: 30s
maxEjectionPercent: 50
subsets:
- name: v1
labels:
version: v1
- name: v2
labels:
version: v2
trafficPolicy:
loadBalancer:
simple: ROUND_ROBIN---
ArgoCD
Application Resource
File: templates/argocd-application.yaml
{{- if .Values.argocd.application.enabled }}
apiVersion: argoproj.io/v1alpha1
kind: Application
metadata:
name: {{ include "mychart.fullname" . }}
namespace: {{ .Values.argocd.application.namespace | default "argocd" }}
labels:
{{- include "mychart.labels" . | nindent 4 }}
{{- with .Values.argocd.application.finalizers }}
finalizers:
{{- toYaml . | nindent 4 }}
{{- end }}
spec:
project: {{ .Values.argocd.application.project | default "default" }}
source:
repoURL: {{ required "argocd.application.source.repoURL is required!" .Values.argocd.application.source.repoURL }}
targetRevision: {{ .Values.argocd.application.source.targetRevision | default "HEAD" }}
path: {{ .Values.argocd.application.source.path }}
{{- with .Values.argocd.application.source.helm }}
helm:
{{- toYaml . | nindent 6 }}
{{- end }}
destination:
server: {{ .Values.argocd.application.destination.server | default "https://kubernetes.default.svc" }}
namespace: {{ .Values.argocd.application.destination.namespace | default .Release.Namespace }}
syncPolicy:
{{- with .Values.argocd.application.syncPolicy }}
{{- toYaml . | nindent 4 }}
{{- end }}
{{- with .Values.argocd.application.ignoreDifferences }}
ignoreDifferences:
{{- toYaml . | nindent 4 }}
{{- end }}
{{- end }}Corresponding values.yaml:
argocd:
application:
enabled: false
namespace: argocd
project: default
finalizers:
- resources-finalizer.argocd.argoproj.io
source:
repoURL: https://github.com/example/repo
targetRevision: main
path: charts/mychart
helm:
releaseName: mychart
valueFiles:
- values.yaml
values: ""
destination:
server: https://kubernetes.default.svc
namespace: default
syncPolicy:
automated:
prune: true
selfHeal: true
allowEmpty: false
syncOptions:
- CreateNamespace=true
- PruneLast=true
retry:
limit: 5
backoff:
duration: 5s
factor: 2
maxDuration: 3m
ignoreDifferences: []AppProject Resource
File: templates/argocd-appproject.yaml
{{- if .Values.argocd.appProject.enabled }}
apiVersion: argoproj.io/v1alpha1
kind: AppProject
metadata:
name: {{ .Values.argocd.appProject.name }}
namespace: {{ .Values.argocd.appProject.namespace | default "argocd" }}
labels:
{{- include "mychart.labels" . | nindent 4 }}
{{- with .Values.argocd.appProject.finalizers }}
finalizers:
{{- toYaml . | nindent 4 }}
{{- end }}
spec:
description: {{ .Values.argocd.appProject.description }}
sourceRepos:
{{- range .Values.argocd.appProject.sourceRepos }}
- {{ . | quote }}
{{- end }}
destinations:
{{- range .Values.argocd.appProject.destinations }}
- namespace: {{ .namespace }}
server: {{ .server }}
{{- end }}
{{- with .Values.argocd.appProject.clusterResourceWhitelist }}
clusterResourceWhitelist:
{{- toYaml . | nindent 4 }}
{{- end }}
{{- with .Values.argocd.appProject.namespaceResourceWhitelist }}
namespaceResourceWhitelist:
{{- toYaml . | nindent 4 }}
{{- end }}
{{- end }}Corresponding values.yaml:
argocd:
appProject:
enabled: false
name: myproject
namespace: argocd
description: "My ArgoCD Project"
finalizers:
- resources-finalizer.argocd.argoproj.io
sourceRepos:
- '*'
destinations:
- namespace: '*'
server: https://kubernetes.default.svc
clusterResourceWhitelist:
- group: '*'
kind: '*'
namespaceResourceWhitelist: []---
Sealed Secrets
SealedSecret Resource
File: templates/sealedsecret.yaml
{{- if .Values.sealedSecret.enabled }}
apiVersion: bitnami.com/v1alpha1
kind: SealedSecret
metadata:
name: {{ include "mychart.fullname" . }}
labels:
{{- include "mychart.labels" . | nindent 4 }}
spec:
encryptedData:
{{- range $key, $value := .Values.sealedSecret.encryptedData }}
{{ $key }}: {{ $value }}
{{- end }}
template:
metadata:
name: {{ include "mychart.fullname" . }}
labels:
{{- include "mychart.labels" . | nindent 8 }}
type: {{ .Values.sealedSecret.type | default "Opaque" }}
{{- end }}Corresponding values.yaml:
sealedSecret:
enabled: false
type: Opaque
encryptedData: {}
# API_KEY: AgBy3i4OJSWK+PiTySYZZA9rO43cGDEq...
# DATABASE_PASSWORD: AgBy3i4OJSWK+PiTySYZZA9rO43cGDEq...---
External Secrets Operator
ExternalSecret Resource
File: templates/externalsecret.yaml
{{- if .Values.externalSecret.enabled }}
apiVersion: external-secrets.io/v1beta1
kind: ExternalSecret
metadata:
name: {{ include "mychart.fullname" . }}
labels:
{{- include "mychart.labels" . | nindent 4 }}
spec:
secretStoreRef:
name: {{ .Values.externalSecret.secretStoreRef.name }}
kind: {{ .Values.externalSecret.secretStoreRef.kind | default "SecretStore" }}
target:
name: {{ include "mychart.fullname" . }}
{{- with .Values.externalSecret.target.creationPolicy }}
creationPolicy: {{ . }}
{{- end }}
{{- with .Values.externalSecret.target.template }}
template:
{{- toYaml . | nindent 6 }}
{{- end }}
{{- with .Values.externalSecret.refreshInterval }}
refreshInterval: {{ . }}
{{- end }}
{{- if .Values.externalSecret.data }}
data:
{{- range .Values.externalSecret.data }}
- secretKey: {{ .secretKey }}
remoteRef:
key: {{ .remoteRef.key }}
{{- with .remoteRef.property }}
property: {{ . }}
{{- end }}
{{- with .remoteRef.version }}
version: {{ . }}
{{- end }}
{{- end }}
{{- end }}
{{- if .Values.externalSecret.dataFrom }}
dataFrom:
{{- range .Values.externalSecret.dataFrom }}
- extract:
key: {{ .extract.key }}
{{- with .extract.property }}
property: {{ . }}
{{- end }}
{{- end }}
{{- end }}
{{- end }}Corresponding values.yaml:
externalSecret:
enabled: false
secretStoreRef:
name: vault-backend
kind: SecretStore
target:
creationPolicy: Owner
template:
type: Opaque
refreshInterval: 1h
data:
- secretKey: API_KEY
remoteRef:
key: secret/data/myapp
property: api_key
- secretKey: DATABASE_PASSWORD
remoteRef:
key: secret/data/myapp
property: db_password
dataFrom: []
# dataFrom:
# - extract:
# key: secret/data/myappSecretStore Resource
File: templates/secretstore.yaml
{{- if .Values.externalSecret.secretStore.create }}
apiVersion: external-secrets.io/v1beta1
kind: SecretStore
metadata:
name: {{ .Values.externalSecret.secretStore.name }}
labels:
{{- include "mychart.labels" . | nindent 4 }}
spec:
provider:
{{- if .Values.externalSecret.secretStore.provider.vault }}
vault:
server: {{ .Values.externalSecret.secretStore.provider.vault.server }}
path: {{ .Values.externalSecret.secretStore.provider.vault.path }}
version: {{ .Values.externalSecret.secretStore.provider.vault.version | default "v2" }}
auth:
{{- toYaml .Values.externalSecret.secretStore.provider.vault.auth | nindent 8 }}
{{- else if .Values.externalSecret.secretStore.provider.aws }}
aws:
service: {{ .Values.externalSecret.secretStore.provider.aws.service }}
region: {{ .Values.externalSecret.secretStore.provider.aws.region }}
{{- with .Values.externalSecret.secretStore.provider.aws.auth }}
auth:
{{- toYaml . | nindent 8 }}
{{- end }}
{{- else if .Values.externalSecret.secretStore.provider.gcpsm }}
gcpsm:
projectID: {{ .Values.externalSecret.secretStore.provider.gcpsm.projectID }}
{{- with .Values.externalSecret.secretStore.provider.gcpsm.auth }}
auth:
{{- toYaml . | nindent 8 }}
{{- end }}
{{- end }}
{{- end }}Corresponding values.yaml:
externalSecret:
secretStore:
create: false
name: vault-backend
provider:
vault:
server: https://vault.example.com
path: secret
version: v2
auth:
kubernetes:
mountPath: kubernetes
role: myapp
serviceAccountRef:
name: mychart
# aws:
# service: SecretsManager
# region: us-east-1
# auth:
# jwt:
# serviceAccountRef:
# name: mychart
# gcpsm:
# projectID: my-project
# auth:
# workloadIdentity:
# clusterLocation: us-central1
# clusterName: my-cluster
# serviceAccountRef:
# name: mychart---
Gateway API
The Gateway API is the evolution of Kubernetes Ingress, providing more expressive and extensible routing capabilities. It's becoming the standard for north-south traffic management in Kubernetes.
GatewayClass Resource
File: templates/gatewayclass.yaml
{{- if .Values.gatewayAPI.gatewayClass.create }}
apiVersion: gateway.networking.k8s.io/v1
kind: GatewayClass
metadata:
name: {{ .Values.gatewayAPI.gatewayClass.name }}
labels:
{{- include "mychart.labels" . | nindent 4 }}
{{- with .Values.gatewayAPI.gatewayClass.annotations }}
annotations:
{{- toYaml . | nindent 4 }}
{{- end }}
spec:
controllerName: {{ required "gatewayAPI.gatewayClass.controllerName is required!" .Values.gatewayAPI.gatewayClass.controllerName }}
{{- with .Values.gatewayAPI.gatewayClass.parametersRef }}
parametersRef:
{{- toYaml . | nindent 4 }}
{{- end }}
{{- with .Values.gatewayAPI.gatewayClass.description }}
description: {{ . | quote }}
{{- end }}
{{- end }}Corresponding values.yaml:
gatewayAPI:
gatewayClass:
create: false
name: my-gateway-class
# Controller implementations:
# - gateway.nginx.org/nginx-gateway-controller (NGINX Gateway Fabric)
# - gateway.envoyproxy.io/gatewayclass-controller (Envoy Gateway)
# - istio.io/gateway-controller (Istio)
# - projectcontour.io/gateway-controller (Contour)
# - traefik.io/gateway-controller (Traefik)
controllerName: gateway.nginx.org/nginx-gateway-controller
annotations: {}
description: "Production gateway class"
parametersRef: {}
# parametersRef:
# group: gateway.nginx.org
# kind: NginxProxy
# name: nginx-proxy-configGateway Resource
File: templates/gateway.yaml
{{- if .Values.gatewayAPI.gateway.enabled }}
apiVersion: gateway.networking.k8s.io/v1
kind: Gateway
metadata:
name: {{ include "mychart.fullname" . }}
namespace: {{ .Values.gatewayAPI.gateway.namespace | default .Release.Namespace }}
labels:
{{- include "mychart.labels" . | nindent 4 }}
{{- with .Values.gatewayAPI.gateway.annotations }}
annotations:
{{- toYaml . | nindent 4 }}
{{- end }}
spec:
gatewayClassName: {{ required "gatewayAPI.gateway.gatewayClassName is required!" .Values.gatewayAPI.gateway.gatewayClassName }}
{{- with .Values.gatewayAPI.gateway.addresses }}
addresses:
{{- toYaml . | nindent 4 }}
{{- end }}
listeners:
{{- range .Values.gatewayAPI.gateway.listeners }}
- name: {{ .name }}
hostname: {{ .hostname | quote }}
port: {{ .port }}
protocol: {{ .protocol }}
{{- with .tls }}
tls:
{{- toYaml . | nindent 6 }}
{{- end }}
{{- with .allowedRoutes }}
allowedRoutes:
{{- toYaml . | nindent 6 }}
{{- end }}
{{- end }}
{{- with .Values.gatewayAPI.gateway.infrastructure }}
infrastructure:
{{- toYaml . | nindent 4 }}
{{- end }}
{{- end }}Corresponding values.yaml:
gatewayAPI:
gateway:
enabled: false
namespace: "" # defaults to release namespace
gatewayClassName: my-gateway-class
annotations: {}
addresses: []
# addresses:
# - type: IPAddress
# value: 10.0.0.1
listeners:
- name: http
hostname: "*.example.com"
port: 80
protocol: HTTP
allowedRoutes:
namespaces:
from: Same
kinds:
- kind: HTTPRoute
- name: https
hostname: "*.example.com"
port: 443
protocol: HTTPS
tls:
mode: Terminate
certificateRefs:
- name: example-com-tls
kind: Secret
allowedRoutes:
namespaces:
from: Same
kinds:
- kind: HTTPRoute
infrastructure: {}
# infrastructure:
# labels:
# app: my-gateway
# annotations:
# service.beta.kubernetes.io/aws-load-balancer-type: nlbHTTPRoute Resource
File: templates/httproute.yaml
{{- if .Values.gatewayAPI.httpRoute.enabled }}
apiVersion: gateway.networking.k8s.io/v1
kind: HTTPRoute
metadata:
name: {{ include "mychart.fullname" . }}
namespace: {{ .Release.Namespace }}
labels:
{{- include "mychart.labels" . | nindent 4 }}
{{- with .Values.gatewayAPI.httpRoute.annotations }}
annotations:
{{- toYaml . | nindent 4 }}
{{- end }}
spec:
{{- with .Values.gatewayAPI.httpRoute.parentRefs }}
parentRefs:
{{- toYaml . | nindent 4 }}
{{- end }}
{{- with .Values.gatewayAPI.httpRoute.hostnames }}
hostnames:
{{- toYaml . | nindent 4 }}
{{- end }}
rules:
{{- range .Values.gatewayAPI.httpRoute.rules }}
- {{- with .matches }}
matches:
{{- toYaml . | nindent 6 }}
{{- end }}
{{- with .filters }}
filters:
{{- toYaml . | nindent 6 }}
{{- end }}
backendRefs:
{{- range .backendRefs }}
- name: {{ .name | default (include "mychart.fullname" $) }}
port: {{ .port | default $.Values.service.port }}
{{- with .weight }}
weight: {{ . }}
{{- end }}
{{- with .kind }}
kind: {{ . }}
{{- end }}
{{- with .namespace }}
namespace: {{ . }}
{{- end }}
{{- end }}
{{- with .timeouts }}
timeouts:
{{- toYaml . | nindent 6 }}
{{- end }}
{{- end }}
{{- end }}Corresponding values.yaml:
gatewayAPI:
httpRoute:
enabled: false
annotations: {}
parentRefs:
- name: my-gateway
namespace: default
sectionName: https
hostnames:
- app.example.com
rules:
# Simple routing to backend service
- matches:
- path:
type: PathPrefix
value: /
backendRefs:
- name: "" # defaults to chart fullname
port: 0 # defaults to service.port
weight: 100
# Path-based routing with multiple backends
- matches:
- path:
type: PathPrefix
value: /api
- path:
type: PathPrefix
value: /v1
backendRefs:
- name: api-service
port: 8080
weight: 90
- name: api-service-canary
port: 8080
weight: 10
# Header-based routing
- matches:
- headers:
- name: X-Version
value: beta
backendRefs:
- name: beta-service
port: 8080
# Method-based routing
- matches:
- method: POST
path:
type: Exact
value: /webhook
backendRefs:
- name: webhook-service
port: 8080
timeouts:
request: 30sHTTPRoute with Filters
File: templates/httproute-advanced.yaml
{{- if .Values.gatewayAPI.httpRouteAdvanced.enabled }}
apiVersion: gateway.networking.k8s.io/v1
kind: HTTPRoute
metadata:
name: {{ include "mychart.fullname" . }}-advanced
labels:
{{- include "mychart.labels" . | nindent 4 }}
spec:
parentRefs:
- name: {{ .Values.gatewayAPI.httpRouteAdvanced.gatewayRef }}
hostnames:
{{- range .Values.gatewayAPI.httpRouteAdvanced.hostnames }}
- {{ . | quote }}
{{- end }}
rules:
{{- if .Values.gatewayAPI.httpRouteAdvanced.redirectHttpToHttps }}
# HTTP to HTTPS redirect
- matches:
- path:
type: PathPrefix
value: /
filters:
- type: RequestRedirect
requestRedirect:
scheme: https
statusCode: 301
{{- end }}
{{- if .Values.gatewayAPI.httpRouteAdvanced.urlRewrite }}
# URL rewrite
- matches:
- path:
type: PathPrefix
value: {{ .Values.gatewayAPI.httpRouteAdvanced.urlRewrite.matchPath }}
filters:
- type: URLRewrite
urlRewrite:
path:
type: ReplacePrefixMatch
replacePrefixMatch: {{ .Values.gatewayAPI.httpRouteAdvanced.urlRewrite.replacePath }}
backendRefs:
- name: {{ include "mychart.fullname" . }}
port: {{ .Values.service.port }}
{{- end }}
{{- if .Values.gatewayAPI.httpRouteAdvanced.requestHeaderModifier }}
# Request header modification
- matches:
- path:
type: PathPrefix
value: /
filters:
- type: RequestHeaderModifier
requestHeaderModifier:
{{- with .Values.gatewayAPI.httpRouteAdvanced.requestHeaderModifier.set }}
set:
{{- range . }}
- name: {{ .name }}
value: {{ .value | quote }}
{{- end }}
{{- end }}
{{- with .Values.gatewayAPI.httpRouteAdvanced.requestHeaderModifier.add }}
add:
{{- range . }}
- name: {{ .name }}
value: {{ .value | quote }}
{{- end }}
{{- end }}
{{- with .Values.gatewayAPI.httpRouteAdvanced.requestHeaderModifier.remove }}
remove:
{{- toYaml . | nindent 12 }}
{{- end }}
backendRefs:
- name: {{ include "mychart.fullname" . }}
port: {{ .Values.service.port }}
{{- end }}
{{- end }}Corresponding values.yaml:
gatewayAPI:
httpRouteAdvanced:
enabled: false
gatewayRef: my-gateway
hostnames:
- app.example.com
# HTTP to HTTPS redirect
redirectHttpToHttps: false
# URL rewrite configuration
urlRewrite:
matchPath: /old-api
replacePath: /api/v2
# Request header modification
requestHeaderModifier:
set:
- name: X-Forwarded-Proto
value: https
add:
- name: X-Request-ID
value: "{{ .Release.Name }}"
remove:
- X-Internal-HeaderGRPCRoute Resource
File: templates/grpcroute.yaml
{{- if .Values.gatewayAPI.grpcRoute.enabled }}
apiVersion: gateway.networking.k8s.io/v1
kind: GRPCRoute
metadata:
name: {{ include "mychart.fullname" . }}-grpc
labels:
{{- include "mychart.labels" . | nindent 4 }}
spec:
parentRefs:
{{- range .Values.gatewayAPI.grpcRoute.parentRefs }}
- name: {{ .name }}
{{- with .namespace }}
namespace: {{ . }}
{{- end }}
{{- with .sectionName }}
sectionName: {{ . }}
{{- end }}
{{- end }}
{{- with .Values.gatewayAPI.grpcRoute.hostnames }}
hostnames:
{{- toYaml . | nindent 4 }}
{{- end }}
rules:
{{- range .Values.gatewayAPI.grpcRoute.rules }}
- {{- with .matches }}
matches:
{{- toYaml . | nindent 6 }}
{{- end }}
backendRefs:
{{- range .backendRefs }}
- name: {{ .name }}
port: {{ .port }}
{{- with .weight }}
weight: {{ . }}
{{- end }}
{{- end }}
{{- end }}
{{- end }}Corresponding values.yaml:
gatewayAPI:
grpcRoute:
enabled: false
parentRefs:
- name: my-gateway
sectionName: grpc
hostnames:
- grpc.example.com
rules:
# Route all gRPC traffic
- backendRefs:
- name: grpc-service
port: 50051
weight: 100
# Method-specific routing
- matches:
- method:
service: myservice.MyService
method: MyMethod
backendRefs:
- name: grpc-service-v2
port: 50051ReferenceGrant Resource
File: templates/referencegrant.yaml
{{- if .Values.gatewayAPI.referenceGrant.enabled }}
apiVersion: gateway.networking.k8s.io/v1
kind: ReferenceGrant
metadata:
name: {{ include "mychart.fullname" . }}-grant
namespace: {{ .Values.gatewayAPI.referenceGrant.namespace | default .Release.Namespace }}
labels:
{{- include "mychart.labels" . | nindent 4 }}
spec:
from:
{{- range .Values.gatewayAPI.referenceGrant.from }}
- group: {{ .group }}
kind: {{ .kind }}
namespace: {{ .namespace }}
{{- end }}
to:
{{- range .Values.gatewayAPI.referenceGrant.to }}
- group: {{ .group | default "" | quote }}
kind: {{ .kind }}
{{- with .name }}
name: {{ . }}
{{- end }}
{{- end }}
{{- end }}Corresponding values.yaml:
gatewayAPI:
referenceGrant:
enabled: false
namespace: "" # defaults to release namespace
# Allow Gateway in gateway-ns to reference Secrets in this namespace
from:
- group: gateway.networking.k8s.io
kind: Gateway
namespace: gateway-ns
to:
- group: ""
kind: Secret
# name: specific-secret # optional: restrict to specific secret---
KEDA
KEDA (Kubernetes Event-driven Autoscaling) provides event-driven autoscaling for Kubernetes workloads. It can scale based on events from various sources like message queues, databases, HTTP requests, and more.
ScaledObject Resource
File: templates/scaledobject.yaml
{{- if .Values.keda.enabled }}
apiVersion: keda.sh/v1alpha1
kind: ScaledObject
metadata:
name: {{ include "mychart.fullname" . }}
labels:
{{- include "mychart.labels" . | nindent 4 }}
{{- with .Values.keda.annotations }}
annotations:
{{- toYaml . | nindent 4 }}
{{- end }}
spec:
scaleTargetRef:
apiVersion: {{ .Values.keda.scaleTargetRef.apiVersion | default "apps/v1" }}
kind: {{ .Values.keda.scaleTargetRef.kind | default "Deployment" }}
name: {{ .Values.keda.scaleTargetRef.name | default (include "mychart.fullname" .) }}
{{- with .Values.keda.scaleTargetRef.envSourceContainerName }}
envSourceContainerName: {{ . }}
{{- end }}
pollingInterval: {{ .Values.keda.pollingInterval | default 30 }}
cooldownPeriod: {{ .Values.keda.cooldownPeriod | default 300 }}
{{- with .Values.keda.idleReplicaCount }}
idleReplicaCount: {{ . }}
{{- end }}
minReplicaCount: {{ .Values.keda.minReplicaCount | default 0 }}
maxReplicaCount: {{ .Values.keda.maxReplicaCount | default 100 }}
{{- with .Values.keda.fallback }}
fallback:
{{- toYaml . | nindent 4 }}
{{- end }}
{{- with .Values.keda.advanced }}
advanced:
{{- toYaml . | nindent 4 }}
{{- end }}
triggers:
{{- range .Values.keda.triggers }}
- type: {{ .type }}
{{- with .name }}
name: {{ . }}
{{- end }}
metadata:
{{- range $key, $value := .metadata }}
{{ $key }}: {{ $value | quote }}
{{- end }}
{{- with .authenticationRef }}
authenticationRef:
{{- toYaml . | nindent 6 }}
{{- end }}
{{- with .metricType }}
metricType: {{ . }}
{{- end }}
{{- end }}
{{- end }}Corresponding values.yaml:
keda:
enabled: false
annotations: {}
scaleTargetRef:
apiVersion: apps/v1
kind: Deployment
name: "" # defaults to chart fullname
envSourceContainerName: "" # optional: container to source env from
pollingInterval: 30 # seconds
cooldownPeriod: 300 # seconds
idleReplicaCount: 0 # scale to 0 when idle (optional)
minReplicaCount: 1
maxReplicaCount: 10
fallback:
failureThreshold: 3
replicas: 2
advanced:
restoreToOriginalReplicaCount: true
horizontalPodAutoscalerConfig:
name: "" # custom HPA name
behavior:
scaleDown:
stabilizationWindowSeconds: 300
policies:
- type: Percent
value: 100
periodSeconds: 15
triggers:
# CPU-based scaling
- type: cpu
metricType: Utilization
metadata:
value: "80"
# Memory-based scaling
- type: memory
metricType: Utilization
metadata:
value: "80"
# Prometheus metrics
- type: prometheus
metadata:
serverAddress: http://prometheus:9090
metricName: http_requests_total
query: sum(rate(http_requests_total{deployment="mychart"}[2m]))
threshold: "100"
# RabbitMQ queue length
- type: rabbitmq
metadata:
host: amqp://rabbitmq:5672
queueName: myqueue
queueLength: "50"
authenticationRef:
name: rabbitmq-auth
# Kafka consumer lag
- type: kafka
metadata:
bootstrapServers: kafka:9092
consumerGroup: my-group
topic: my-topic
lagThreshold: "100"
# Redis list length
- type: redis
metadata:
address: redis:6379
listName: mylist
listLength: "10"
# AWS SQS queue
- type: aws-sqs-queue
metadata:
queueURL: https://sqs.us-east-1.amazonaws.com/123456789/myqueue
queueLength: "50"
awsRegion: us-east-1
authenticationRef:
name: aws-credentials
# Cron-based scaling
- type: cron
metadata:
timezone: America/New_York
start: "0 8 * * 1-5" # 8 AM weekdays
end: "0 18 * * 1-5" # 6 PM weekdays
desiredReplicas: "10"ScaledJob Resource
File: templates/scaledjob.yaml
{{- if .Values.keda.scaledJob.enabled }}
apiVersion: keda.sh/v1alpha1
kind: ScaledJob
metadata:
name: {{ include "mychart.fullname" . }}-job
labels:
{{- include "mychart.labels" . | nindent 4 }}
spec:
jobTargetRef:
parallelism: {{ .Values.keda.scaledJob.parallelism | default 1 }}
completions: {{ .Values.keda.scaledJob.completions | default 1 }}
activeDeadlineSeconds: {{ .Values.keda.scaledJob.activeDeadlineSeconds | default 600 }}
backoffLimit: {{ .Values.keda.scaledJob.backoffLimit | default 6 }}
template:
spec:
{{- with .Values.imagePullSecrets }}
imagePullSecrets:
{{- toYaml . | nindent 10 }}
{{- end }}
restartPolicy: {{ .Values.keda.scaledJob.restartPolicy | default "Never" }}
containers:
- name: {{ .Chart.Name }}-job
image: "{{ .Values.image.repository }}:{{ .Values.image.tag | default .Chart.AppVersion }}"
imagePullPolicy: {{ .Values.image.pullPolicy }}
{{- with .Values.keda.scaledJob.command }}
command:
{{- toYaml . | nindent 12 }}
{{- end }}
{{- with .Values.keda.scaledJob.args }}
args:
{{- toYaml . | nindent 12 }}
{{- end }}
{{- with .Values.keda.scaledJob.env }}
env:
{{- toYaml . | nindent 12 }}
{{- end }}
resources:
{{- toYaml .Values.keda.scaledJob.resources | nindent 12 }}
pollingInterval: {{ .Values.keda.scaledJob.pollingInterval | default 30 }}
successfulJobsHistoryLimit: {{ .Values.keda.scaledJob.successfulJobsHistoryLimit | default 5 }}
failedJobsHistoryLimit: {{ .Values.keda.scaledJob.failedJobsHistoryLimit | default 5 }}
maxReplicaCount: {{ .Values.keda.scaledJob.maxReplicaCount | default 100 }}
scalingStrategy:
strategy: {{ .Values.keda.scaledJob.scalingStrategy | default "default" }}
triggers:
{{- range .Values.keda.scaledJob.triggers }}
- type: {{ .type }}
metadata:
{{- range $key, $value := .metadata }}
{{ $key }}: {{ $value | quote }}
{{- end }}
{{- with .authenticationRef }}
authenticationRef:
{{- toYaml . | nindent 6 }}
{{- end }}
{{- end }}
{{- end }}Corresponding values.yaml:
keda:
scaledJob:
enabled: false
parallelism: 1
completions: 1
activeDeadlineSeconds: 600
backoffLimit: 6
restartPolicy: Never
pollingInterval: 30
successfulJobsHistoryLimit: 5
failedJobsHistoryLimit: 5
maxReplicaCount: 100
scalingStrategy: default # default, custom, accurate
command: []
args: []
env: []
resources:
limits:
cpu: 100m
memory: 128Mi
requests:
cpu: 100m
memory: 128Mi
triggers:
- type: aws-sqs-queue
metadata:
queueURL: https://sqs.us-east-1.amazonaws.com/123456789/myqueue
queueLength: "5"
awsRegion: us-east-1TriggerAuthentication Resource
File: templates/triggerauthentication.yaml
{{- if .Values.keda.authentication.enabled }}
apiVersion: keda.sh/v1alpha1
kind: TriggerAuthentication
metadata:
name: {{ include "mychart.fullname" . }}-auth
labels:
{{- include "mychart.labels" . | nindent 4 }}
spec:
{{- with .Values.keda.authentication.secretTargetRef }}
secretTargetRef:
{{- range . }}
- parameter: {{ .parameter }}
name: {{ .name }}
key: {{ .key }}
{{- end }}
{{- end }}
{{- with .Values.keda.authentication.env }}
env:
{{- range . }}
- parameter: {{ .parameter }}
name: {{ .name }}
{{- with .containerName }}
containerName: {{ . }}
{{- end }}
{{- end }}
{{- end }}
{{- with .Values.keda.authentication.podIdentity }}
podIdentity:
provider: {{ .provider }}
{{- with .identityId }}
identityId: {{ . }}
{{- end }}
{{- end }}
{{- with .Values.keda.authentication.hashiCorpVault }}
hashiCorpVault:
address: {{ .address }}
authentication: {{ .authentication }}
{{- with .role }}
role: {{ . }}
{{- end }}
{{- with .mount }}
mount: {{ . }}
{{- end }}
credential:
{{- toYaml .credential | nindent 6 }}
secrets:
{{- range .secrets }}
- parameter: {{ .parameter }}
key: {{ .key }}
path: {{ .path }}
{{- end }}
{{- end }}
{{- end }}Corresponding values.yaml:
keda:
authentication:
enabled: false
# Secret-based authentication
secretTargetRef:
- parameter: connection
name: rabbitmq-secret
key: connectionString
- parameter: password
name: db-secret
key: password
# Environment variable authentication
env:
- parameter: awsAccessKeyID
name: AWS_ACCESS_KEY_ID
- parameter: awsSecretAccessKey
name: AWS_SECRET_ACCESS_KEY
# Pod identity (Azure, AWS IRSA, GCP Workload Identity)
podIdentity:
provider: azure # azure, aws-eks, gcp
identityId: "" # optional: specific identity
# HashiCorp Vault authentication
hashiCorpVault:
address: https://vault.example.com
authentication: token # token, kubernetes
role: my-role
mount: secret
credential:
token: vault-token # or serviceAccount for kubernetes auth
secrets:
- parameter: connection
key: connectionString
path: secret/data/myapp---
VerticalPodAutoscaler (VPA)
The VerticalPodAutoscaler automatically adjusts the CPU and memory reservations for pods to help "right-size" your applications.
VerticalPodAutoscaler Resource
File: templates/vpa.yaml
{{- if .Values.vpa.enabled }}
apiVersion: autoscaling.k8s.io/v1
kind: VerticalPodAutoscaler
metadata:
name: {{ include "mychart.fullname" . }}
labels:
{{- include "mychart.labels" . | nindent 4 }}
{{- with .Values.vpa.annotations }}
annotations:
{{- toYaml . | nindent 4 }}
{{- end }}
spec:
targetRef:
apiVersion: {{ .Values.vpa.targetRef.apiVersion | default "apps/v1" }}
kind: {{ .Values.vpa.targetRef.kind | default "Deployment" }}
name: {{ .Values.vpa.targetRef.name | default (include "mychart.fullname" .) }}
updatePolicy:
updateMode: {{ .Values.vpa.updatePolicy.updateMode | default "Auto" }}
{{- with .Values.vpa.updatePolicy.minReplicas }}
minReplicas: {{ . }}
{{- end }}
{{- with .Values.vpa.resourcePolicy }}
resourcePolicy:
containerPolicies:
{{- range .containerPolicies }}
- containerName: {{ .containerName | default "*" }}
{{- with .mode }}
mode: {{ . }}
{{- end }}
{{- with .minAllowed }}
minAllowed:
{{- toYaml . | nindent 8 }}
{{- end }}
{{- with .maxAllowed }}
maxAllowed:
{{- toYaml . | nindent 8 }}
{{- end }}
{{- with .controlledResources }}
controlledResources:
{{- toYaml . | nindent 8 }}
{{- end }}
{{- with .controlledValues }}
controlledValues: {{ . }}
{{- end }}
{{- end }}
{{- end }}
{{- with .Values.vpa.recommenders }}
recommenders:
{{- toYaml . | nindent 4 }}
{{- end }}
{{- end }}Corresponding values.yaml:
vpa:
enabled: false
annotations: {}
targetRef:
apiVersion: apps/v1
kind: Deployment
name: "" # defaults to chart fullname
updatePolicy:
# Auto - VPA assigns resource requests on pod creation and updates on existing pods
# Recreate - VPA assigns resource requests on pod creation and kills existing pods
# Initial - VPA only assigns requests on pod creation, never updates
# Off - VPA does not update pods, only provides recommendations
updateMode: "Auto"
minReplicas: 1 # minimum replicas for VPA to act on
resourcePolicy:
containerPolicies:
# Apply to all containers
- containerName: "*"
minAllowed:
cpu: 50m
memory: 64Mi
maxAllowed:
cpu: 2
memory: 4Gi
controlledResources:
- cpu
- memory
controlledValues: RequestsAndLimits # RequestsOnly or RequestsAndLimits
# Specific container policy
- containerName: my-container
mode: Auto # Auto or Off
minAllowed:
cpu: 100m
memory: 128Mi
maxAllowed:
cpu: 1
memory: 2Gi
# Custom recommenders (optional, requires VPA 0.11+)
recommenders:
- name: custom-recommenderVPA with Recommendation Only
For applications where you want VPA recommendations without automatic updates:
File: templates/vpa-recommendation.yaml
{{- if .Values.vpa.recommendationOnly.enabled }}
apiVersion: autoscaling.k8s.io/v1
kind: VerticalPodAutoscaler
metadata:
name: {{ include "mychart.fullname" . }}-recommendation
labels:
{{- include "mychart.labels" . | nindent 4 }}
vpa-mode: recommendation-only
spec:
targetRef:
apiVersion: apps/v1
kind: {{ .Values.vpa.recommendationOnly.kind | default "Deployment" }}
name: {{ include "mychart.fullname" . }}
updatePolicy:
updateMode: "Off"
resourcePolicy:
containerPolicies:
- containerName: "*"
minAllowed:
cpu: {{ .Values.vpa.recommendationOnly.minCpu | default "25m" }}
memory: {{ .Values.vpa.recommendationOnly.minMemory | default "32Mi" }}
maxAllowed:
cpu: {{ .Values.vpa.recommendationOnly.maxCpu | default "4" }}
memory: {{ .Values.vpa.recommendationOnly.maxMemory | default "8Gi" }}
{{- end }}Corresponding values.yaml:
vpa:
recommendationOnly:
enabled: false
kind: Deployment
minCpu: 25m
minMemory: 32Mi
maxCpu: 4
maxMemory: 8Gi---
General Best Practices
1. Conditional Resource Creation
Always wrap CRD resources in conditional blocks:
{{- if .Values.certificate.enabled }}
# CRD resource
{{- end }}2. Required Values
Use required for critical CRD fields:
email: {{ required "certManager.clusterIssuer.acme.email is required!" .Values.certManager.clusterIssuer.acme.email }}3. Documentation
Document CRD dependencies in Chart.yaml:
annotations:
operatorDependencies: |
- name: cert-manager
version: ">=1.12.0"
url: https://cert-manager.io/docs/installation/4. Default Values
Provide sensible defaults for all CRD fields:
certificate:
enabled: false
duration: 2160h
renewBefore: 360h5. Version Awareness
Be explicit about API versions:
apiVersion: cert-manager.io/v1 # Not v1alpha1 or v1beta16. Helper Usage
Use chart helpers for consistent labeling:
metadata:
name: {{ include "mychart.fullname" . }}-tls
labels:
{{- include "mychart.labels" . | nindent 4 }}7. Testing
Always test CRD resources with:
helm templateto verify renderinghelm lintto check syntaxkubectl apply --dry-run=serverto validate against cluster
---
Resources
Helm Template Functions Reference
Comprehensive guide to Helm template functions with practical examples.
Essential Template Functions
1. required - Enforce Required Values
Fails template rendering if a value is not provided.
# Syntax
{{ required "error message" .Values.some.value }}
# Example
image: {{ required "image.repository is required!" .Values.image.repository }}
tag: {{ required "image.tag is required!" .Values.image.tag }}When to use:
- Critical configuration values that must be set
- Values without sensible defaults
- Production deployments where safety is paramount
2. default - Provide Fallback Values
Provides a default value if the value is empty or undefined.
# Syntax
{{ .Values.some.value | default "default-value" }}
# Examples
replicas: {{ .Values.replicaCount | default 1 }}
image:
tag: {{ .Values.image.tag | default .Chart.AppVersion }}
pullPolicy: {{ .Values.image.pullPolicy | default "IfNotPresent" }}
# Computed defaults
serviceName: {{ .Values.service.name | default (printf "%s-svc" (include "mychart.fullname" .)) }}When to use:
- Optional configuration values
- Values with sensible defaults
- Fallback to computed values
3. quote - Safely Quote Strings
Wraps a value in double quotes, escaping special characters.
# Syntax
{{ .Values.some.value | quote }}
# Examples
env:
- name: DATABASE_URL
value: {{ .Values.database.url | quote }}
- name: API_KEY
value: {{ .Values.api.key | quote }}
# With pipeline
image: {{ .Values.image.repository | default "nginx" | quote }}When to use:
- String values in environment variables
- Configuration values that might contain special characters
- YAML string fields that need guaranteed quoting
4. include - Include Templates with Pipeline Support
Includes a named template and allows piping the result to other functions.
# Syntax
{{ include "template-name" context }}
# Examples
metadata:
labels:
{{- include "mychart.labels" . | nindent 4 }}
# With modifications
annotations:
checksum: {{ include "mychart.config" . | sha256sum }}When to use:
- Including helpers or named templates
- When you need to pipe template output to other functions
- Prefer
includeovertemplatefor better composability
5. template - Include Templates (No Pipeline)
Includes a named template (cannot be piped).
# Syntax
{{ template "template-name" context }}
# Example
metadata:
labels:
{{ template "mychart.labels" . }}When to use:
- Simple template inclusion without modification
- Legacy templates (prefer
includefor new code)
6. toYaml - Convert to YAML
Converts an object to YAML format.
# Syntax
{{ .Values.some.object | toYaml }}
# Examples
{{- with .Values.resources }}
resources:
{{- toYaml . | nindent 2 }}
{{- end }}
{{- with .Values.nodeSelector }}
nodeSelector:
{{- toYaml . | nindent 2 }}
{{- end }}When to use:
- Complex nested objects from values.yaml
- Resource specifications (limits, requests)
- Selector, affinity, and toleration definitions
7. fromYaml - Parse YAML String
Parses a YAML string into an object.
# Syntax
{{ .Values.yamlString | fromYaml }}
# Example
{{- $config := .Values.configYaml | fromYaml }}
{{- if $config.enabled }}
# Use $config values
{{- end }}When to use:
- Parsing YAML strings from values
- Dynamic configuration loading
8. tpl - Render String as Template
Evaluates a string as a Go template.
# Syntax
{{ tpl .Values.templateString . }}
# Example values.yaml
config:
message: "Release: {{ .Release.Name }}"
# Template
data:
message: {{ tpl .Values.config.message . }}When to use:
- Dynamic template strings in values.yaml
- User-provided template content
- Advanced configuration patterns
9. nindent / indent - Control Indentation
Adds newline and indentation, or just indentation.
# nindent - newline + indent
metadata:
labels:
{{- include "mychart.labels" . | nindent 4 }}
# indent - just indent (no newline)
data:
config: |
{{ .Values.config | indent 4 }}When to use:
nindentfor YAML blocks after keysindentfor multi-line string content
10. printf - Format Strings
Formats a string using Go's fmt.Sprintf syntax.
# Syntax
{{ printf "format" arg1 arg2 }}
# Examples
name: {{ printf "%s-%s" .Release.Name .Chart.Name }}
label: {{ printf "app.kubernetes.io/name: %s" .Chart.Name }}When to use:
- Constructing names or identifiers
- String formatting and concatenation
Logical Functions
if / else / else if - Conditionals
{{- if .Values.ingress.enabled }}
# Ingress resource
{{- end }}
{{- if eq .Values.service.type "LoadBalancer" }}
# LoadBalancer config
{{- else if eq .Values.service.type "NodePort" }}
# NodePort config
{{- else }}
# Default config
{{- end }}and / or / not - Boolean Logic
{{- if and .Values.enabled (not .Values.debug) }}
# Enabled and not debug
{{- end }}
{{- if or .Values.useSSL .Values.production }}
# SSL or production
{{- end }}with - Scope Context
{{- with .Values.nodeSelector }}
nodeSelector:
{{- toYaml . | nindent 2 }}
{{- end }}Note: Inside with, . refers to the scoped value. Use $ for root context.
Comparison Functions
eq- Equal:{{ if eq .Values.env "prod" }}ne- Not equal:{{ if ne .Values.replicas 1 }}lt- Less than:{{ if lt .Values.replicas 3 }}le- Less or equal:{{ if le .Values.replicas 5 }}gt- Greater than:{{ if gt .Values.replicas 1 }}ge- Greater or equal:{{ if ge .Values.replicas 3 }}
String Functions (Sprig)
Basic String Operations
# upper / lower
name: {{ .Values.name | upper }}
label: {{ .Values.env | lower }}
# trim / trimSuffix / trimPrefix
name: {{ .Values.name | trim }}
name: {{ .Values.name | trimSuffix "-" }}
# trunc - truncate to length
name: {{ .Values.longName | trunc 63 | trimSuffix "-" }}
# replace
chart: {{ .Chart.Name | replace "." "-" }}
# contains
{{- if contains "prod" .Values.environment }}
# Production settings
{{- end }}String Testing
# hasPrefix / hasSuffix
{{- if hasPrefix "prod-" .Values.name }}
# empty - test if value is empty
{{- if not (empty .Values.optional) }}List Functions (Sprig)
list - Create Lists
{{- $myList := list "item1" "item2" "item3" }}append / prepend
{{- $list := list "a" "b" }}
{{- $list = append $list "c" }}
{{- $list = prepend $list "z" }}first / rest / last / initial
first: {{ first .Values.items }}
last: {{ last .Values.items }}has - Check Membership
{{- if has "production" .Values.environments }}Dict/Map Functions (Sprig)
dict - Create Dictionary
{{- $myDict := dict "key1" "value1" "key2" "value2" }}
{{- include "mychart.helper" $myDict }}set / unset
{{- $_ := set .Values "newKey" "newValue" }}
{{- $_ := unset .Values "oldKey" }}hasKey
{{- if hasKey .Values "optional" }}merge - Merge Dictionaries
{{- $merged := merge .Values.override .Values.defaults }}Type Functions
typeOf / kindOf
{{- $type := typeOf .Values.someValue }}int / int64 / float64 / toString
port: {{ .Values.port | int }}
cpu: {{ .Values.cpu | toString }}Crypto Functions
sha256sum - SHA-256 Hash
annotations:
checksum/config: {{ include (print $.Template.BasePath "/configmap.yaml") . | sha256sum }}b64enc / b64dec - Base64
data:
password: {{ .Values.password | b64enc }}Date Functions
now - Current Time
annotations:
timestamp: {{ now | date "2006-01-02T15:04:05Z07:00" }}date - Format Date
date: {{ now | date "2006-01-02" }}Regex Functions
regexMatch / regexFind / regexReplace
{{- if regexMatch "^prod-" .Values.name }}
{{- $version := .Values.image.tag | regexFind "[0-9]+" }}
label: {{ .Values.name | regexReplaceAll "[^a-z0-9-]" "-" }}Flow Control Functions
range - Iterate
# Range over list
{{- range .Values.items }}
- {{ . }}
{{- end }}
# Range over map
{{- range $key, $value := .Values.config }}
{{ $key }}: {{ $value }}
{{- end }}
# Range with index
{{- range $index, $item := .Values.items }}
{{ $index }}: {{ $item }}
{{- end }}Lookup Function (Cluster Queries)
Query existing Kubernetes resources (use with caution).
{{- $secret := lookup "v1" "Secret" .Release.Namespace "my-secret" }}
{{- if $secret }}
# Secret exists
data:
password: {{ $secret.data.password }}
{{- else }}
# Create new secret
{{- end }}⚠️ Warning: lookup queries the cluster, which:
- Breaks the declarative model
- May cause issues with
helm template - Can lead to non-reproducible builds
- Should be used sparingly
Best Practices
1. Validation First
# Validate required values
name: {{ required "name is required" .Values.name }}
port: {{ required "port is required" .Values.port }}2. Provide Defaults
# Always provide sensible defaults
replicas: {{ .Values.replicaCount | default 1 }}
image:
pullPolicy: {{ .Values.image.pullPolicy | default "IfNotPresent" }}3. Quote Strings
# Quote string values in env vars and annotations
env:
- name: DATABASE_URL
value: {{ .Values.database.url | quote }}4. Use include Over template
# Prefer include for composability
labels:
{{- include "mychart.labels" . | nindent 2 }}
# Not: {{ template "mychart.labels" . }}5. Use toYaml for Complex Objects
# Let toYaml handle complex structures
{{- with .Values.resources }}
resources:
{{- toYaml . | nindent 2 }}
{{- end }}6. Proper Indentation
# Use nindent for YAML blocks
metadata:
labels:
{{- include "mychart.labels" . | nindent 4 }}
# Use indent for multi-line strings
data:
config: |
{{ .Values.config | indent 4 }}7. Whitespace Control
# Use {{- and -}} to control whitespace
{{- if .Values.enabled }}
content
{{- end }}8. Fail Fast with required
# Fail early if critical values missing
database:
host: {{ required "database.host is required!" .Values.database.host }}Common Patterns
ConfigMap Checksum
Trigger pod restart when ConfigMap changes:
annotations:
checksum/config: {{ include (print $.Template.BasePath "/configmap.yaml") . | sha256sum }}Conditional Resource Creation
{{- if .Values.ingress.enabled }}
apiVersion: networking.k8s.io/v1
kind: Ingress
# ...
{{- end }}Dynamic Names
name: {{ include "mychart.fullname" . }}
serviceName: {{ include "mychart.fullname" . }}-svcPassing Custom Context
{{- include "mychart.container" (dict "root" . "container" .Values.mainContainer) }}Then in helper:
{{- define "mychart.container" -}}
{{- $root := .root }}
{{- $container := .container }}
name: {{ $container.name }}
image: {{ $container.image }}
namespace: {{ $root.Release.Namespace }}
{{- end }}Resources
#!/bin/bash
# Script to generate standard Helm helpers (_helpers.tpl)
# Usage: bash generate_standard_helpers.sh <chart-name> <chart-directory> [options]
#
# Options:
# --force Overwrite existing _helpers.tpl without prompting
set -e
# Colors for output
RED='\033[0;31m'
YELLOW='\033[1;33m'
NC='\033[0m' # No Color
# Default values
FORCE=false
# Function to print error and exit
error_exit() {
echo -e "${RED}ERROR: $1${NC}" >&2
exit 1
}
# Function to print warning
warn() {
echo -e "${YELLOW}WARNING: $1${NC}" >&2
}
# Function to validate chart name (DNS-1123 subdomain)
validate_chart_name() {
local name="$1"
# Check if empty
if [ -z "$name" ]; then
error_exit "Chart name cannot be empty"
fi
# Check length (max 63 characters for DNS-1123)
if [ ${#name} -gt 63 ]; then
error_exit "Chart name '${name}' exceeds 63 characters (DNS-1123 limit)"
fi
# Check for valid characters (lowercase alphanumeric and hyphens)
if ! echo "$name" | grep -qE '^[a-z0-9]([-a-z0-9]*[a-z0-9])?$'; then
error_exit "Chart name '${name}' is invalid. Must:
- Start with a lowercase letter or number
- Contain only lowercase letters, numbers, and hyphens
- End with a lowercase letter or number
Examples: myapp, my-app, app1, my-cool-app"
fi
# Check for consecutive hyphens
if echo "$name" | grep -q '\-\-'; then
error_exit "Chart name '${name}' contains consecutive hyphens (not allowed)"
fi
}
# Function to show usage
show_usage() {
cat << EOF
Usage: $0 <chart-name> <chart-directory> [options]
Arguments:
chart-name Name of the Helm chart (must be DNS-1123 compliant)
chart-directory Directory of the chart (must exist)
Options:
--force Overwrite existing _helpers.tpl without prompting
-h, --help Show this help message
Examples:
$0 myapp ./myapp
$0 my-service ./charts/my-service --force
EOF
exit 0
}
# Parse arguments
POSITIONAL_ARGS=()
while [[ $# -gt 0 ]]; do
case $1 in
-h|--help)
show_usage
;;
--force)
FORCE=true
shift
;;
-*)
error_exit "Unknown option: $1. Use --help for usage information."
;;
*)
POSITIONAL_ARGS+=("$1")
shift
;;
esac
done
# Restore positional parameters
set -- "${POSITIONAL_ARGS[@]}"
# Check required arguments
if [ $# -lt 2 ]; then
echo "Usage: $0 <chart-name> <chart-directory> [options]"
echo "Example: $0 myapp ./myapp"
echo "Use --help for more information"
exit 1
fi
CHART_NAME="$1"
CHART_DIR="$2"
HELPERS_FILE="${CHART_DIR}/templates/_helpers.tpl"
# Validate chart name
validate_chart_name "$CHART_NAME"
# Check if chart directory exists
if [ ! -d "$CHART_DIR" ]; then
error_exit "Chart directory does not exist: ${CHART_DIR}"
fi
# Check if _helpers.tpl already exists
if [ -f "$HELPERS_FILE" ]; then
if [ "$FORCE" = true ]; then
warn "Overwriting existing _helpers.tpl at ${HELPERS_FILE}"
else
echo -e "${YELLOW}File already exists: ${HELPERS_FILE}${NC}"
read -p "Do you want to overwrite it? [y/N] " -n 1 -r
echo
if [[ ! $REPLY =~ ^[Yy]$ ]]; then
echo "Aborted. Use --force to overwrite without prompting."
exit 1
fi
fi
fi
# Ensure templates directory exists
mkdir -p "${CHART_DIR}/templates"
# Generate _helpers.tpl
cat > "${HELPERS_FILE}" <<EOF
{{/*
Expand the name of the chart.
*/}}
{{- define "${CHART_NAME}.name" -}}
{{- default .Chart.Name .Values.nameOverride | trunc 63 | trimSuffix "-" }}
{{- end }}
{{/*
Create a default fully qualified app name.
*/}}
{{- define "${CHART_NAME}.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 "${CHART_NAME}.chart" -}}
{{- printf "%s-%s" .Chart.Name .Chart.Version | replace "+" "_" | trunc 63 | trimSuffix "-" }}
{{- end }}
{{/*
Common labels
*/}}
{{- define "${CHART_NAME}.labels" -}}
helm.sh/chart: {{ include "${CHART_NAME}.chart" . }}
{{ include "${CHART_NAME}.selectorLabels" . }}
{{- if .Chart.AppVersion }}
app.kubernetes.io/version: {{ .Chart.AppVersion | quote }}
{{- end }}
app.kubernetes.io/managed-by: {{ .Release.Service }}
{{- end }}
{{/*
Selector labels
*/}}
{{- define "${CHART_NAME}.selectorLabels" -}}
app.kubernetes.io/name: {{ include "${CHART_NAME}.name" . }}
app.kubernetes.io/instance: {{ .Release.Name }}
{{- end }}
{{/*
Create the name of the service account to use
*/}}
{{- define "${CHART_NAME}.serviceAccountName" -}}
{{- if .Values.serviceAccount.create }}
{{- default (include "${CHART_NAME}.fullname" .) .Values.serviceAccount.name }}
{{- else }}
{{- default "default" .Values.serviceAccount.name }}
{{- end }}
{{- end }}
EOF
echo "✅ Generated _helpers.tpl at ${HELPERS_FILE}"
echo " Chart name: ${CHART_NAME}"
echo " Standard helpers included:"
echo " - ${CHART_NAME}.name"
echo " - ${CHART_NAME}.fullname"
echo " - ${CHART_NAME}.chart"
echo " - ${CHART_NAME}.labels"
echo " - ${CHART_NAME}.selectorLabels"
echo " - ${CHART_NAME}.serviceAccountName"
#!/usr/bin/env bash
set -euo pipefail
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
SKILL_DIR="$(cd "$SCRIPT_DIR/.." && pwd)"
GENERATOR_SCRIPT="$SKILL_DIR/scripts/generate_chart_structure.sh"
SCHEMA_FILE="$SKILL_DIR/assets/values-schema-template.json"
TMP_DIR="$(mktemp -d)"
cleanup() {
rm -rf "$TMP_DIR"
}
trap cleanup EXIT
PASS=0
FAIL=0
pass() {
echo " PASS: $1"
PASS=$((PASS + 1))
}
fail() {
echo " FAIL: $1"
FAIL=$((FAIL + 1))
}
run_expect_success() {
local label="$1"
shift
local log_file="$TMP_DIR/success_${PASS}_${FAIL}.log"
set +e
"$@" >"$log_file" 2>&1
local rc=$?
set -e
if [[ $rc -eq 0 ]]; then
pass "$label"
else
fail "$label (exit $rc)"
sed 's/^/ /' "$log_file"
fi
}
run_expect_failure() {
local label="$1"
shift
local log_file="$TMP_DIR/failure_${PASS}_${FAIL}.log"
set +e
"$@" >"$log_file" 2>&1
local rc=$?
set -e
if [[ $rc -ne 0 ]]; then
pass "$label"
else
fail "$label (unexpected success)"
fi
}
assert_file_contains_literal() {
local label="$1"
local file_path="$2"
local expected_text="$3"
if grep -Fq -- "$expected_text" "$file_path"; then
pass "$label"
else
fail "$label (missing '$expected_text' in $file_path)"
fi
}
if ! command -v helm >/dev/null 2>&1; then
echo "FAIL: helm CLI is required for this regression suite"
exit 1
fi
if ! command -v python3 >/dev/null 2>&1; then
echo "FAIL: python3 is required for schema assertions"
exit 1
fi
VALID_SHA256_HASH="94a00394bc5a8ef503fb59db0a7d0ae9e1119866e8aee8ba40cd864cea69ea1a"
VALID_SHA512_HASH=""
for _ in {1..128}; do
VALID_SHA512_HASH="${VALID_SHA512_HASH}a"
done
CHART_ROOT="$TMP_DIR/charts"
mkdir -p "$CHART_ROOT"
echo "[1] Digest validation"
run_expect_failure \
"reject empty sha256 hash" \
bash "$GENERATOR_SCRIPT" invaliddigestempty "$CHART_ROOT" \
--image "ghcr.io/acme/api@sha256:" --force
run_expect_failure \
"reject short sha256 hash" \
bash "$GENERATOR_SCRIPT" invaliddigestshort "$CHART_ROOT" \
--image "ghcr.io/acme/api@sha256:abcd" --force
run_expect_failure \
"reject non-hex sha256 hash" \
bash "$GENERATOR_SCRIPT" invaliddigesthex "$CHART_ROOT" \
--image "ghcr.io/acme/api@sha256:zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz" --force
run_expect_failure \
"reject unsupported digest algorithm" \
bash "$GENERATOR_SCRIPT" invaliddigestalgo "$CHART_ROOT" \
--image "ghcr.io/acme/api@md5:0123456789abcdef0123456789abcdef" --force
run_expect_success \
"accept valid sha256 digest" \
bash "$GENERATOR_SCRIPT" digestoksha256 "$CHART_ROOT" \
--image "ghcr.io/acme/api@sha256:${VALID_SHA256_HASH}" --with-templates --force
run_expect_success \
"accept valid sha512 digest" \
bash "$GENERATOR_SCRIPT" digestoksha512 "$CHART_ROOT" \
--image "ghcr.io/acme/api@sha512:${VALID_SHA512_HASH}" --force
SHA256_VALUES="$CHART_ROOT/digestoksha256/values.yaml"
SHA512_VALUES="$CHART_ROOT/digestoksha512/values.yaml"
assert_file_contains_literal "sha256 digest written to values.yaml" "$SHA256_VALUES" " digest: \"sha256:${VALID_SHA256_HASH}\""
assert_file_contains_literal "sha512 digest written to values.yaml" "$SHA512_VALUES" " digest: \"sha512:${VALID_SHA512_HASH}\""
assert_file_contains_literal "digest chart keeps tag empty" "$SHA256_VALUES" " tag: \"\""
echo "[2] Tag and digest conflict handling"
run_expect_failure \
"reject digest image with explicit --tag" \
bash "$GENERATOR_SCRIPT" digesttagflag "$CHART_ROOT" \
--image "ghcr.io/acme/api@sha256:${VALID_SHA256_HASH}" --tag "1.2.3" --force
run_expect_failure \
"reject image refs containing both tag and digest" \
bash "$GENERATOR_SCRIPT" digesttaginline "$CHART_ROOT" \
--image "ghcr.io/acme/api:1.2.3@sha256:${VALID_SHA256_HASH}" --force
echo "[3] Registry port parsing"
run_expect_success \
"parse registry port image without tag" \
bash "$GENERATOR_SCRIPT" regportplain "$CHART_ROOT" \
--image "registry.example.com:5000/team/api" --force
run_expect_success \
"parse registry port image with tag" \
bash "$GENERATOR_SCRIPT" regporttag "$CHART_ROOT" \
--image "registry.example.com:5000/team/api:1.2.0" --force
REGPORT_PLAIN_VALUES="$CHART_ROOT/regportplain/values.yaml"
REGPORT_TAG_VALUES="$CHART_ROOT/regporttag/values.yaml"
assert_file_contains_literal "registry-port plain repository preserved" "$REGPORT_PLAIN_VALUES" " repository: registry.example.com:5000/team/api"
assert_file_contains_literal "registry-port plain tag remains empty" "$REGPORT_PLAIN_VALUES" " tag: \"\""
assert_file_contains_literal "registry-port tagged repository auto-split" "$REGPORT_TAG_VALUES" " repository: registry.example.com:5000/team/api"
assert_file_contains_literal "registry-port tagged value extracted" "$REGPORT_TAG_VALUES" " tag: \"1.2.0\""
echo "[4] Helm render smoke tests"
run_expect_success \
"helm lint --strict generated digest chart" \
helm lint --strict "$CHART_ROOT/digestoksha256"
run_expect_success \
"helm template generated digest chart" \
helm template test "$CHART_ROOT/digestoksha256"
echo "[5] Schema guardrails"
SCHEMA_LOG="$TMP_DIR/schema_assertions.log"
set +e
python3 - "$SCHEMA_FILE" >"$SCHEMA_LOG" 2>&1 <<'PY'
import json
import sys
with open(sys.argv[1], "r", encoding="utf-8") as fh:
schema = json.load(fh)
config_map_data = schema["properties"]["configMap"]["properties"]["data"]
secret_string_data = schema["properties"]["secret"]["properties"]["stringData"]
if config_map_data.get("additionalProperties", {}).get("type") != "string":
raise SystemExit("configMap.data must enforce string values")
if secret_string_data.get("additionalProperties", {}).get("type") != "string":
raise SystemExit("secret.stringData must enforce string values")
PY
schema_rc=$?
set -e
if [[ $schema_rc -eq 0 ]]; then
pass "schema constrains ConfigMap/Secret maps to string values"
else
fail "schema constrains ConfigMap/Secret maps to string values"
sed 's/^/ /' "$SCHEMA_LOG"
fi
echo ""
echo "Completed with PASS=$PASS FAIL=$FAIL"
if [[ $FAIL -ne 0 ]]; then
exit 1
fi
Related skills
FAQ
What does helm-generator scaffold?
helm-generator scaffolds Helm charts with Chart.yaml, values.yaml, and templates for Deployments, Services, Ingress, and Secrets with defaults suited to staging and production Kubernetes releases.
Does helm-generator deploy to a cluster?
helm-generator produces Helm chart files for helm install or CI pipelines. Cluster deployment still requires helm upgrade or a CD pipeline after the chart scaffold is generated.