helm
Helm Values YAML Example: Complete Configuration
Intermediate
13 minutes
October 2026
CloudOpsGuide Team
Helm Values YAML Example: Complete Configuration
Comprehensive Helm values.yaml example with all common configuration options explained and best practices.
Table of Contents
- Understanding values.yaml
- Complete Annotated Example
- Environment-Specific Values
- Overriding Values
- Common Patterns
- Validation and Schema
Understanding values.yaml
The values.yaml file defines the default configuration for a Helm chart. Values are accessed in templates via .Values.
# values.yaml
replicaCount: 3
image:
repository: nginx
tag: "1.25"
# templates/deployment.yaml
replicas: {{ .Values.replicaCount }}
image: "{{ .Values.image.repository }}:{{ .Values.image.tag }}"
Complete Annotated Example
This is a production-ready values.yaml covering all common sections:
# ---------------------------------------------------------
# Application basics
# ---------------------------------------------------------
nameOverride: ""
fullnameOverride: ""
replicaCount: 3
# ---------------------------------------------------------
# Container image
# ---------------------------------------------------------
image:
repository: myregistry.io/my-app
pullPolicy: IfNotPresent # Always | IfNotPresent | Never
tag: "" # defaults to .Chart.AppVersion
digest: "" # optional: pin by sha256 digest
imagePullSecrets:
- name: regcred
# ---------------------------------------------------------
# Pod configuration
# ---------------------------------------------------------
podLabels:
environment: production
team: platform
podAnnotations:
prometheus.io/scrape: "true"
prometheus.io/port: "9090"
podSecurityContext:
runAsNonRoot: true
runAsUser: 1000
fsGroup: 1000
securityContext:
allowPrivilegeEscalation: false
readOnlyRootFilesystem: true
capabilities:
drop:
- ALL
# ---------------------------------------------------------
# Environment variables
# ---------------------------------------------------------
env:
- name: LOG_LEVEL
value: "info"
- name: DATABASE_URL
valueFrom:
secretKeyRef:
name: db-credentials
key: url
envFrom:
- configMapRef:
name: app-config
# ---------------------------------------------------------
# Service
# ---------------------------------------------------------
service:
type: ClusterIP # ClusterIP | NodePort | LoadBalancer
port: 80
targetPort: 8080
annotations: {}
# ---------------------------------------------------------
# Ingress
# ---------------------------------------------------------
ingress:
enabled: true
className: nginx
annotations:
cert-manager.io/cluster-issuer: letsencrypt-prod
nginx.ingress.kubernetes.io/rate-limit: "100"
hosts:
- host: app.example.com
paths:
- path: /
pathType: Prefix
tls:
- secretName: app-tls
hosts:
- app.example.com
# ---------------------------------------------------------
# Resources
# ---------------------------------------------------------
resources:
requests:
memory: "256Mi"
cpu: "250m"
limits:
memory: "512Mi"
cpu: "500m"
# ---------------------------------------------------------
# Autoscaling
# ---------------------------------------------------------
autoscaling:
enabled: true
minReplicas: 3
maxReplicas: 20
targetCPUUtilizationPercentage: 70
targetMemoryUtilizationPercentage: 80
# ---------------------------------------------------------
# Health probes
# ---------------------------------------------------------
livenessProbe:
httpGet:
path: /healthz
port: http
initialDelaySeconds: 30
periodSeconds: 10
failureThreshold: 3
readinessProbe:
httpGet:
path: /ready
port: http
initialDelaySeconds: 5
periodSeconds: 5
startupProbe:
httpGet:
path: /healthz
port: http
failureThreshold: 30
periodSeconds: 5
# ---------------------------------------------------------
# Persistence
# ---------------------------------------------------------
persistence:
enabled: true
storageClass: "managed-premium"
accessMode: ReadWriteOnce
size: 10Gi
mountPath: /data
# ---------------------------------------------------------
# Scheduling
# ---------------------------------------------------------
nodeSelector:
kubernetes.io/os: linux
tolerations:
- key: "dedicated"
operator: "Equal"
value: "workload"
effect: "NoSchedule"
affinity:
podAntiAffinity:
preferredDuringSchedulingIgnoredDuringExecution:
- weight: 100
podAffinityTerm:
labelSelector:
matchLabels:
app: my-app
topologyKey: kubernetes.io/hostname
topologySpreadConstraints:
- maxSkew: 1
topologyKey: topology.kubernetes.io/zone
whenUnsatisfiable: ScheduleAnyway
# ---------------------------------------------------------
# Pod disruption budget
# ---------------------------------------------------------
podDisruptionBudget:
enabled: true
minAvailable: 1
# ---------------------------------------------------------
# Service account
# ---------------------------------------------------------
serviceAccount:
create: true
name: ""
annotations:
azure.workload.identity/client-id: "xxxx-xxxx-xxxx" # Azure Workload Identity
# eks.amazonaws.com/role-arn: "arn:aws:iam::123456789012:role/my-role" # AWS IRSA
# ---------------------------------------------------------
# Dependencies / subcharts
# ---------------------------------------------------------
postgresql:
enabled: true
auth:
database: myapp
username: myapp
primary:
persistence:
size: 20Gi
redis:
enabled: false
Environment-Specific Values
Keep a base values.yaml plus per-environment overrides:
charts/my-app/
├── values.yaml # defaults
├── values-dev.yaml
├── values-staging.yaml
└── values-prod.yaml
values-dev.yaml
replicaCount: 1
image:
pullPolicy: Always
tag: "dev"
resources:
requests:
memory: "128Mi"
cpu: "100m"
ingress:
hosts:
- host: app.dev.example.com
paths:
- path: /
pathType: Prefix
autoscaling:
enabled: false
values-prod.yaml
replicaCount: 5
image:
pullPolicy: IfNotPresent
resources:
requests:
memory: "512Mi"
cpu: "500m"
limits:
memory: "1Gi"
cpu: "1000m"
podDisruptionBudget:
enabled: true
minAvailable: 2
Overriding Values
Command Line
# Single value
helm install my-app ./my-app --set image.tag=1.2.3
# Multiple values
helm install my-app ./my-app \
--set image.tag=1.2.3 \
--set replicaCount=5
# Array values
helm install my-app ./my-app \
--set 'ingress.hosts[0].host=app.example.com'
# From file
helm install my-app ./my-app -f values-prod.yaml
# Layered values files (later files take precedence)
helm install my-app ./my-app -f values.yaml -f values-prod.yaml
Priority Order (lowest to highest)
values.yaml(defaults)-fflag files (in order given)--setflags (highest precedence)
Common Patterns
Conditional Features
# values.yaml
monitoring:
enabled: true
# templates/servicemonitor.yaml
{{- if .Values.monitoring.enabled }}
apiVersion: monitoring.coreos.com/v1
kind: ServiceMonitor
...
{{- end }}
Required Values (fail fast)
# templates/deployment.yaml
image: "{{ required "image.repository is required" .Values.image.repository }}"
Deploying without setting image.repository fails with a clear error.
Sensible Defaults in Templates
tag: {{ .Values.image.tag | default .Chart.AppVersion }}
Validation and Schema
Use values.schema.json to validate user-provided values:
{
"$schema": "https://json-schema.org/draft-07/schema#",
"type": "object",
"required": ["image"],
"properties": {
"replicaCount": {
"type": "integer",
"minimum": 1,
"maximum": 50
},
"image": {
"type": "object",
"required": ["repository"],
"properties": {
"repository": { "type": "string" },
"tag": { "type": "string" },
"pullPolicy": {
"type": "string",
"enum": ["Always", "IfNotPresent", "Never"]
}
}
}
}
}
Useful Commands
# Show the rendered values that will be used
helm template my-app ./my-app -f values-prod.yaml
# Show computed default values
helm show values ./my-app
# Validate templates without deploying
helm lint ./my-app
# Dry run an install
helm install my-app ./my-app --dry-run --debug
# See what values a release is using
helm get values my-app
Best Practices
- Document every value with comments
- Never put secrets in values.yaml — use
valueFrom.secretKeyRefor external secrets - Use
--setsparingly — prefer values files tracked in Git - Define a schema (
values.schema.json) to catch mistakes early - Keep environment diffs minimal — only override what differs
- Quote strings that look like numbers —
port: "8080"is safer thanport: 8080in some contexts
Related Articles
Last Updated: October 2026
Author: CloudOpsGuide Team
Difficulty: Intermediate
Estimated Reading Time: 13 minutes