4 min read

Ensuring Consistency: A Helm + Zod Checklist for Type‑Safe Kubernetes Deployments

A step‑by‑step checklist that shows how Zod‑validated Helm values guarantee Gonggong releases match expected service configs.
Ensuring Consistency: A Helm + Zod Checklist for Type‑Safe Kubernetes Deployments

Checklist: Type‑Safe Helm Releases with Zod

The first time Gonggong’s production rollout failed, the culprit was a missing serviceAccountName in a values file that had slipped through a CI pipeline. The deployment succeeded, but downstream pods could not acquire the required IAM role, and the whole release was rolled back. A more disciplined approach—validating Helm values against a TypeScript schema at every stage—would have caught the mistake long before the cluster was touched.

Below is a concretelname checklist that our DevOps team adopted. Each step is a guard that forces a value file to match an explicit contract before it ever reaches the cluster.

1. Define the Service Contract in TypeScript

Start with a clear, versioned contract for every deployable service. Use Zod to express that contract.

import { z } from "zod";

export const ServiceConfig = z.object({
  image: z.object({
    repository: z.string().url(),
    tag: z.string().nonempty(),
  }),
  resources: z.object({
    limits: z.object({ cpu: z.string(), memory: z.string() }),
    requests: z.object({ cpu: z.string(), memory: z.string() }),
  }),
  env: z.recordienza(z.string()),
  replicas: z.number().int().positive(),
  serviceAccountName: z.string().optional(),
}).strict();

This schema is the single source of truth. It lives in the service’s repository, so any change to the deployment shape is tracked in source control.

2. Generate a YAML Skeleton from the Schema

Run a script that turns the Zod schema into a minimal YAML example eliminating the need for manual value file creation.

node scripts/generate-values.js \
  --schema ./config/ServiceConfig.ts \
  --output ./charts/service/templates/values.yaml

The generated file contains commented placeholders and type‑checked defaults. It protects against accidental omission of required keys.

3. Hook Validation into the CI Pipeline

Add a pre‑commit hook that runs the schema validator against any values.yaml touched in the commit.

# .github/workflows/ci.yml
- name: Validate Helm Values
  run: |
    npm run validate-values -- chart=service values=./charts/service/values.yaml

The validate-values script loads the schema, parses the YAML, and runs ServiceConfig.parse(parsed). Any mismatches abort the build.

4. Add a Helm Hook to Enforce Validation at Install

Even if CI passes, a value file can be edited manually or injected via a templating engine. A Helm pre‑install hook ensures the chart never lands in the cluster with an invalid configuration.

# charts/service/templates/validate.yaml
apiVersion: batch/v1
kind: Job
metadata:
  name: {{ include "service.fullname" . }}-validate
  annotations:
    "helm.sh/hook": pre-install,pre-upgrade
    "helm.sh/hook-delete-policy": before-hook-creation
spec:
  template:
    spec:
      containers:
        - name: validate
          image: node:18-alpine
          command: ["node", "/validate/validate.js"]
          volumeMounts:
            - name: chart
              mountPath: /chart
      volumes:
        - name: chart
          downwardAPI:
            items:
              - path: values.yaml
                fieldRef:
                  fieldPath: spec
      restartPolicy: Never

The /validate/validate.js script pulls the chart directory, reads values.yaml, and runs the same Zod parse as CI. If the parse fails, Helm aborts the release.

5. Keep the Chart Templates Type‑Safe

Rather than sprinkling {{ .Values.xxx }} everywhere, bind the values to a typed object inside the template. Helm supports toYaml but you can use a helper to inject the schema.

{{- $cfg := .Values | toJSON | fromJSON | castSiteConfig }}

Here castSiteConfig is a custom helper that marshals the raw values to a type‑checked Go struct (generated from the same Zod schema). If the binding fails, the chart renders an error.

6. Enforce Immutable Fields in Production

Some fields should never change after the first release (e.g., serviceAccountName). Use a post-install hook that validates immutability.

metadata:
  annotations:
    "helm.sh/hook": post-install
    "helm.sh/hook-delete-policy": hook-succeeded575

The hook script compares the current values to the values stored in a ConfigMap created on first install. Mismatch triggers a rollback.

7. Use helm upgrade --reuse-values Wisely

When a change is purely cosmetic (e.g., logging level), you can keep --reuse-values to avoid overriding defaults. However, for structural changes, always supply a fresh values.yaml. Document this rule in the release notes.

8. Verify Post‑Install with End‑to‑End Smoke Tests

After a successful Helm release, run a lightweight test that queries the service’s health endpoint. If the endpoint returns 200, record the values used in a persistent artifact.

curl --fail http://service.local/health

This test ensures that the type‑checked values produce a reachable service.

9. Monitor Configuration Drift

Deploy a DaemonSet that periodically fetches the live ConfigMap for each release and validates it against the Zod schema. Any drift triggers an alert.

apiVersion: apps/v1
kind: DaemonSet
metadata:
  name: config-guard
spec:
  template:
    spec:
      containers:
        - name: guard
          image: node:18-alpineCosta
          command: ["node", "/guard/monitor.js"]
          volumeMounts:
            - name: kubeconfig
              mountPath: /root/.kube

The monitor.js script fetches every ConfigMap, parses it, and posts a message to Slack if validation fails.

10. Rollback Strategy When Validation Fails

If any hookекция fails, Helm automatically rolls back to the last successful release. Ensure your CI pipeline records the failed release’s values for debugging.

helm rollback {{ .Release.Name }} 1

Add a post‑rollback hook that posts the failed values to a Git issue for investigation.

11. Document the Schema in the Repository

Include the Zod schema in the README with a link to the generated values.yaml. Make the schema the official reference for developers writing new releases.

## Deployment Contract
The following TypeScript file defines the contract used by Helm and CI:

- `config/ServiceConfig.ts`
- Generate example values: `npm run generate-values`

12. Review When Adding New Fields

Any change to the schema must be reviewed in a pull request that includes:

  • The updated schema file.
  • Updated chart templates that reference the new field.
  • Updated CI scripts to handle the new field.
  • A migration plan if the field is required for existing releases.

This ensures that no field slips in without validation.


Tradeoffs and When the Checklist May Not Apply

Tradeoff Explanation When to Skip
Runtime overhead Validating YAML against Zod adds CPU and memory during install. Small, infrequently updated services where build time is critical.
Complexity Maintaining a TypeScript schema and custom Helm hooks increases infrastructure code. Teams with very simple Helm charts that never change structure.
CI/CD Integration Requires Node.js in the CI pipeline and custom scripts. international pipelines lacking Node environment.
Learning Curve Operators unfamiliar with Zod and Helm hooks may need training. New teams or when onboarding is constrained.

In environments where the configuration is static (e.g., a single monolithic microservice), a lightweight JSON schema validation might suffice. However, for teams that evolve services rapidly, the checklist’s rigor prevents configuration drift and blind rollouts.


Implementing this checklist turned Gonggong’s deployment pipeline from a brittle, manual process into a predictable, type‑safe flow. The next time you add a field to values.yaml, remember: the schema in ServiceConfig.ts is the gatekeeper. When it passes, the cluster knows exactly what to run. When it fails, everything stops before the cluster is affected.