ZDDC/helm
ZDDC 607121a9ea feat: example helm charts for zddc-server (production + dev)
Two charts under helm/, both compile zddc-server from source via an
init container — no container image registry, no pre-built binary.
The init container clones the repo at a configured git ref, runs
`go build`, and writes the binary into a shared emptyDir; the main
container is alpine + the freshly built static binary.

helm/zddc-server-prod/  Production-shaped:
                        - gitRef pinned to a stable tag in
                          values.yaml.example (zddc-server-v0.0.7).
                        - imagePullPolicy IfNotPresent.
                        - Slower probe cadence (30s liveness, 10s
                          readiness).
                        - ZDDC_LOG_LEVEL=info.
                        - replicaCount: 1 (operators raise as needed
                          when backed by a shared filesystem).

helm/zddc-server-dev/   Dev/soak-shaped:
                        - gitRef defaults to "main" (rebuilt every pod
                          restart). build-time annotation forces
                          recreate on every helm upgrade.
                        - imagePullPolicy Always on the build image
                          so the latest golang:1.24-alpine is pulled.
                        - Faster probe cadence (10s liveness, 5s
                          readiness) — fail-fast in dev.
                        - ZDDC_LOG_LEVEL=debug. NOTE: debug logs every
                          request's full header map (includes auth
                          tokens / cookies) — this chart is for
                          private dev namespaces only.
                        - Strategy: Recreate (single replica racing
                          on different SHAs would be a mess).

Both charts:

- Wire the ZDDC_* env-var contract (ZDDC_ROOT, ZDDC_ADDR,
  ZDDC_TLS_CERT=none, ZDDC_INSECURE_DIRECT=1, ZDDC_EMAIL_HEADER,
  ZDDC_CORS_ORIGIN, ZDDC_LOG_LEVEL, ZDDC_INDEX_PATH).
- Mount a caller-supplied PVC at ZDDC_ROOT (chart does not create the
  PVC; operators provision storage themselves).
- Optional Ingress (ingress.enabled: true). TLS is expected to be
  terminated upstream of the pod; the pod listens on plain HTTP.
- No secrets in values.yaml.example. ACL email lists go in .zddc files
  inside the data volume; image-pull and TLS secrets are referenced by
  name only.

helm/README.md documents the design rationale (why build from source
instead of using a registry image), a quick-start example, and the
explicit list of what the charts do and don't do.

Note: `helm lint` cannot be run in this dev environment (helm isn't
installed). YAML syntax of Chart.yaml and values.yaml.example
verified via `python3 -c "yaml.safe_load(...)"`. Operators should
run `helm lint` and `helm template` before installing.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-30 09:48:02 -05:00
..
zddc-server-dev feat: example helm charts for zddc-server (production + dev) 2026-04-30 09:48:02 -05:00
zddc-server-prod feat: example helm charts for zddc-server (production + dev) 2026-04-30 09:48:02 -05:00
README.md feat: example helm charts for zddc-server (production + dev) 2026-04-30 09:48:02 -05:00

Helm charts

Two example charts for deploying zddc-server on Kubernetes. Both compile zddc-server from source via an init container — no container image needs to be pulled from a registry, and no binary needs to be built ahead of time. The init container clones the repo at a configured git ref and runs go build; the main container is plain alpine + the freshly built static binary.

Charts

Chart When to use
zddc-server-prod/ Production. Pin zddc.gitRef to a stable tag (zddc-server-vX.Y.Z). Slower probe cadence; image-pull policy IfNotPresent.
zddc-server-dev/ Development / soak. Tracks main by default; helm upgrade triggers a pod recreate so each rollout pulls the latest commit. Faster probes; debug-level logging (request headers logged — sensitive).

The chart values are nearly identical between the two; the differences are encoded as defaults in each chart's values.yaml.example.

Quick start

# Pre-requisite: a PersistentVolumeClaim for ZDDC_ROOT data
kubectl apply -f - <<'EOF'
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
  name: zddc-root
spec:
  accessModes: [ReadWriteMany]    # or RWO if single replica is fine
  resources: { requests: { storage: 100Gi } }
  storageClassName: your-shared-fs   # NFS, CephFS, SMB, etc.
EOF

# Production install
cp helm/zddc-server-prod/values.yaml.example my-prod-values.yaml
$EDITOR my-prod-values.yaml          # set zddc.gitRef, hostnames, etc.
helm install zddc-server-prod helm/zddc-server-prod/ -f my-prod-values.yaml

# Dev install (tracks main HEAD)
cp helm/zddc-server-dev/values.yaml.example my-dev-values.yaml
$EDITOR my-dev-values.yaml
helm install zddc-server-dev helm/zddc-server-dev/ -f my-dev-values.yaml

# Trigger a rebuild from latest main HEAD (dev chart)
helm upgrade zddc-server-dev helm/zddc-server-dev/ -f my-dev-values.yaml

What the chart does and doesn't do

Does:

  • Clones the configured zddc.gitRepo at zddc.gitRef in an init container, builds the Go binary, copies it to a shared emptyDir, and starts the main container against that binary.
  • Wires the ZDDC_* environment-variable contract (root path, addr, email header, CORS allowlist, log level, index path).
  • Mounts a caller-supplied PersistentVolumeClaim at ZDDC_ROOT.
  • Optionally creates an Ingress (ingress.enabled: true).

Does not:

  • Create the PVC. Operators provision storage themselves; the chart only references it by name.
  • Manage TLS for the pod. zddc-server runs in plain HTTP mode behind whatever ingress / authenticating reverse proxy the cluster already has. ZDDC_TLS_CERT=none and ZDDC_INSECURE_DIRECT=1 are hardcoded in the templates because the chart is opinionated about the TLS-terminated-upstream deployment shape.
  • Authenticate users. zddc-server reads the user's email from a header set by the upstream proxy (X-Auth-Request-Email by default). The chart does not deploy oauth2-proxy / nginx-auth-request / Pomerium / etc. — bring your own.
  • Manage secrets. values.yaml.example contains no secrets and never should. ACL email lists belong in .zddc files inside the data volume; image-pull credentials and TLS certs (if you enable ingress TLS) reference Kubernetes secrets you've created separately.

Why build from source instead of using a registry image

Three reasons:

  1. Reproducibility. The init container's logs show exactly which git ref was built. There's no opaque "what did I deploy" question that a registry tag can introduce.
  2. One distribution channel. Codeberg release-asset binaries already exist for direct downloads; the chart compiles its own binary from the same source git ref so there's nothing extra to maintain (no separate image registry, no image-promotion pipeline).
  3. Smaller blast radius. A compromised build image affects only pods that pull during the compromise window. A compromised registry image stays compromised across rollbacks until the digest is rotated.

The cost: every pod start takes 30-60s to clone + go build instead of pulling a pre-baked image. Acceptable for both chart audiences (production rollouts are infrequent; dev rollouts trade build time for tracking-main convenience).

Linting

helm lint helm/zddc-server-prod/
helm lint helm/zddc-server-dev/

# Render to inspect (uses default values from values.yaml.example):
helm template test-prod helm/zddc-server-prod/ \
  --values helm/zddc-server-prod/values.yaml.example