CloudOpsGuide
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

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)

  1. values.yaml (defaults)
  2. -f flag files (in order given)
  3. --set flags (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

  1. Document every value with comments
  2. Never put secrets in values.yaml — use valueFrom.secretKeyRef or external secrets
  3. Use --set sparingly — prefer values files tracked in Git
  4. Define a schema (values.schema.json) to catch mistakes early
  5. Keep environment diffs minimal — only override what differs
  6. Quote strings that look like numbers — port: "8080" is safer than port: 8080 in some contexts

Related Articles


Last Updated: October 2026
Author: CloudOpsGuide Team
Difficulty: Intermediate
Estimated Reading Time: 13 minutes