Helm operations

Production Helm charts: a practical checklist

Walk this list against a packaged chart before you treat a release as production-ready. Each check has a pass fixture, a known failure, and a command you can rerun.

Chart fidelity

Examples use Universal Helm Chart application 0.4.3 on Artifact Hub. Keys match the published values.yaml and values.schema.json. Hostnames and images are sanitized—replace them before apply. The chart default securityContext: {} is empty; production values must harden it.

Chart 0.4.3 — args / multi-port / service.enabled

From application 0.4.3: main container args (default []); Deployment revisionHistoryLimit (default 10); multi-port via containerPorts and service.ports; optional chart-managed Service via service.enabled (default true). When service.ports is set, Ingress / simple HTTPRoute / NOTES use the first entry (templates/_helpers.tpl application.servicePort). Legacy single-port service.name / service.port / service.protocol / service.appProtocol remain supported. Set service.enabled: false when the workload does not need a chart-managed Service. Do not invent other port keys.

Chart 0.4.3 — route.spec

From application 0.4.3, set route.spec to a complete HTTPRoute.spec (parentRefs, hostnames, rules, filters, multi-backendRefs). When route.spec is non-empty, the chart renders it unchanged and ignores the simple route.hostname / route.path / route.gateway generator. Simple route.* remains the default path; route.gateway is required only when route.enabled: true and route.spec is empty. Do not invent shorthand keys like route.headers or route.cors.

Chart 0.4.3 — service.appProtocol

Optional service.appProtocol (default "") sets Service.spec.ports[].appProtocol on the chart-managed Service (templates/7_service.yaml). For Services created by ingressPlain paths with createService: true, set paths[].service.appProtocol (since 0.4.0). Leave empty when you do not need a protocol hint (for example grpc). Do not invent other service.* protocol keys.

Chart 0.4.2 — externalSecretHooks

Behavior change in application 0.4.2: ExternalSecret objects in extraManifests are ordinary resources by default. The chart does not add Helm hooks automatically. Recommend explicit lifecycle/ordering annotations on the manifest (for example argocd.argoproj.io/sync-wave: "-5"). Set externalSecretHooks.enabled: true (default false) only when legacy automatic helm.sh/hook: pre-install,pre-upgrade / hook-weight: "-5" / hook-delete-policy: before-hook-creation behavior is required (templates/1_extra-manifests.yaml). Do not invent other externalSecret* keys.

Versions used

Pass / fail fixtures

Example pack: examples/production-helm-checklist/. Pass path: fixtures/pass-production.yaml (hardened fields). Three fail fixtures cover missing resources, privileged/hostPath, and plaintext secret anti-patterns.

1. Chart.yaml and versioning

# Chart.yaml (consumer wrapper)
dependencies:
  - name: application
    version: "0.4.3"
    repository: https://chaser100.github.io/u-helm-chart

2. values.schema.json

Treat the schema as a CI gate, not optional docs. Run it in CI with helm lint / helm template so invalid types fail before merge.

Pass: integer replicaCount, enum imagePullPolicy.

Fail (captured):

# fail-schema-replicaCount.yaml
replicaCount: "two"
# Error: Invalid type. Expected: integer, given: string

# fail-schema-imagePullPolicy.yaml
imagePullPolicy: Sometimes
# Error: must be one of "Always", "IfNotPresent", "Never"

3. Deterministic naming, selectors, labels

4. Immutable fields and upgrade hazards

5. Secure pod and container defaults

The chart package default is empty securityContext: {}. Production values must set hardening explicitly:

podSecurityContext:
  seccompProfile:
    type: RuntimeDefault
securityContext:
  runAsNonRoot: true
  runAsUser: 10001
  runAsGroup: 10001
  allowPrivilegeEscalation: false
  readOnlyRootFilesystem: true
  capabilities:
    drop: ["ALL"]
serviceAccount:
  create: true
  automount: false

Fail mode: empty security contexts in prod namespaces that enforce Pod Security Standards either reject pods (restricted) or quietly accept weak defaults (baseline without org policy).

6. ServiceAccount and RBAC

7. Probes, resources, disruption

8. HPA

autoscaling:
  enabled: true
  minReplicas: 2
  maxReplicas: 6
  targetCPUUtilizationPercentage: 70

With HPA enabled, HPA owns scale—do not keep overriding replicaCount in GitOps as if it were the source of truth. Keep minReplicas ≥ PDB needs.

9. Hooks, Jobs, and CronJobs

10. Secrets references (never literals)

envSecrets:
  enableEnv: true
  envs:
    - name: DATABASE_URL
      secretName: checklist-api-secrets
      secretKey: DATABASE_URL

Pass: rendered manifest contains secretKeyRef only (see envSecrets above).

Fail: embedding credentials in values or ConfigMaps. Review rendered YAML for sample literals such as password: and DATABASE_URL: postgres, and assert secretKeyRef is present. That is a fixture guard—not proof that arbitrary secrets are absent. Treat full secret hygiene as a review/policy gate.

11. Tests, lint, render, policy

Minimum local gate:

helm lint application-0.4.3.tgz -f fixtures/pass-production.yaml
helm template checklist-api application-0.4.3.tgz -f fixtures/pass-production.yaml -n apps

12. GitOps compatibility and rollback

# Imperative rollback evidence (lab only)
helm rollback checklist-api 1 -n apps
# GitOps rollback: revert the values/chart pin commit and let the reconciler apply

13. Observability

14. Gateway API / kgateway checklist

Prefer simple chart route.* for portable single-host/path HTTPRoute fields; use route.spec when you need the full HTTPRoute.spec. Keep kgateway-specific policies in labelled escape hatches.

route:
  enabled: true
  gateway: external
  gatewayNamespace: kgateway-system
  sectionName: https
  hostname: checklist-api.example.com
  path: /
  pathMatchType: PathPrefix
  timeouts:
    request: 75s
    backendRequest: 75s

Fail (captured): route.enabled: true without route.gatewayroute.gateway is required when route.enabled=true.

15. Positive vs failing examples (summary)

CaseResult
pass-production.yamlLint OK; renders Deployment, Service, SA, HPA, HTTPRoute, PDB, and ServiceMonitor with hardened securityContext and secret refs
fail-schema-replicaCount.yamlSchema rejects string replicaCount
fail-schema-imagePullPolicy.yamlSchema rejects invalid enum
fail-route-missing-gateway.yamlTemplate fails requiring route.gateway

Production gate (copy/paste)

  1. Chart version pinned; changelog reviewed for breaking values.
  2. helm lint and schema validation green.
  3. Rendered manifests: non-root, drop ALL, read-only root (or a documented exception), probes, requests/limits.
  4. Secrets only via Secret references / ExternalSecrets.
  5. Service type ClusterIP; no NodePort, hostPath, or localhost assumptions.
  6. Exposure: one of route.* or ingress.* (dual only during migration).
  7. HTTPRoute parentRefs and Gateway listener allowlist verified; status Accepted/ResolvedRefs.
  8. HPA/PDB coherent; topology strategy documented when it is not a first-class key.
  9. GitOps dry-run/diff clean; rollback owner and revision known.
  10. Observability scrape path exists when ServiceMonitor is enabled.

One chart vs many → · Gateway API vs Ingress → · Ingress NGINX to kgateway → · Browse examples →

Platform engineering with Helm → · Common Kubernetes deployment mistakes → · Kubernetes Secrets best practices → · One chart vs many → · Gateway API vs Ingress → · Ingress NGINX to kgateway → · Browse examples →