Guides: deploy & operate
How to deploy on Kubernetes with Helm
This guide shows you how to run DeltaGlider Proxy in production on Kubernetes with the official Helm chart. For a local proof-of-concept on kind, do the Kubernetes hello world tutorial first.
The chart lives in charts/deltaglider-proxy and is intentionally boring: one Deployment, one Service, a PVC for /data, a rendered config file, and optional Ingress/HPA/PDB/NetworkPolicy. It deploys the same single-port binary used everywhere else — S3 API on /, admin UI on /_/, health on /_/health, metrics on /_/metrics.
1. Create the credentials Secret#
Keep credentials outside the values file: create a Kubernetes Secret outside Helm and point the chart at it.
Minimum filesystem-backed Secret:
apiVersion: v1
kind: Secret
metadata:
name: deltaglider-secrets
type: Opaque
stringData:
DGP_ACCESS_KEY_ID: admin
DGP_SECRET_ACCESS_KEY: replace-me
DGP_BOOTSTRAP_PASSWORD_HASH: "JDJiJDEyJ..."
If you use an S3 storage backend, add the backend credentials too:
DGP_BE_AWS_ACCESS_KEY_ID: "..."
DGP_BE_AWS_SECRET_ACCESS_KEY: "..."
Generate the bootstrap password hash with the binary, and use the printed base64 DGP_BOOTSTRAP_PASSWORD_HASH=... value — do not paste the plaintext password into the Secret:
printf '%s\n' 'your-admin-password' | deltaglider_proxy --set-bootstrap-password
2. Install the chart#
helm upgrade --install dgp ./charts/deltaglider-proxy \
--namespace dgp \
--create-namespace \
--set auth.createSecret=false \
--set auth.existingSecret=deltaglider-secrets
3. Choose the storage backend#
If you use the filesystem backend (the chart default), object data and the encrypted IAM DB live on the chart PVC:
storage:
filesystem: /data/storage
access:
iam_mode: gui
advanced:
listen_addr: "0.0.0.0:9000"
log_level: "deltaglider_proxy=info,tower_http=warn"
Size the PVC for your data:
persistence:
enabled: true
storageClass: fast-ssd
size: 500Gi
If you use an S3 backend, render the S3 config via config.inline and keep the backend credentials in the Secret — they are deliberately not stored in config.inline; the proxy reads them from DGP_BE_AWS_ACCESS_KEY_ID / DGP_BE_AWS_SECRET_ACCESS_KEY:
auth:
createSecret: false
existingSecret: deltaglider-secrets
config:
inline: |
storage:
s3: https://s3.eu-central-1.amazonaws.com
region: eu-central-1
force_path_style: false
access:
iam_mode: gui
advanced:
listen_addr: "0.0.0.0:9000"
cache_size_mb: 2048
log_level: "deltaglider_proxy=info,tower_http=warn"
Why the config is mounted under /data#
The binary derives the encrypted IAM database path from DGP_CONFIG: dirname($DGP_CONFIG)/deltaglider_config.db. The chart therefore mounts the rendered config as /data/deltaglider_proxy.yaml, so the config DB lands at /data/deltaglider_config.db — both on the writable PVC. Do not mount the config under a read-only ConfigMap directory such as /config, or IAM will be disabled because SQLite cannot create the encrypted DB.
4. Expose it with Ingress#
Route the whole host to the service. The admin UI and S3 API share one listener — do not split paths.
ingress:
enabled: true
className: nginx
annotations:
nginx.ingress.kubernetes.io/proxy-body-size: "0"
hosts:
- host: s3.acme.example
paths:
- path: /
pathType: Prefix
tls:
- secretName: dgp-tls
hosts:
- s3.acme.example
env:
- name: DGP_TRUST_PROXY_HEADERS
value: "true"
Set DGP_TRUST_PROXY_HEADERS=true only when the proxy is actually behind a trusted ingress controller — it affects rate limiting and IAM aws:SourceIp conditions. If the pod is reachable without the ingress, leave it false. If your ingress controller has a request read-timeout (most do), raise it for large uploads — see How to serve TLS.
5. Validate before deploy#
helm lint ./charts/deltaglider-proxy
helm template dgp ./charts/deltaglider-proxy
helm lint ./charts/deltaglider-proxy -f charts/deltaglider-proxy/examples/s3-values.yaml
Validate the application config inside config.inline separately:
deltaglider_proxy config lint deltaglider_proxy.yaml
Security defaults#
The chart ships the hardening the Dockerfile expects — leave these alone:
- non-root user/group
999 readOnlyRootFilesystem: true,allowPrivilegeEscalation: false, all Linux capabilities dropped- service account token automount disabled by default
/tmpprovided byemptyDir; persistent/datavolume for mutable state
Replicas#
replicaCount defaults to 1 — keep it there. The chart has no multipart-aware
routing: with more than one replica behind a round-robin Service, S3 multipart uploads
fail with NoSuchUpload, because the state of an upload lives only on the pod that
started it. For a multi-pod deployment, use the official operator instead — it deploys
the consistent-hashing router that this requires: How to scale out with the Kubernetes
operator.
With config sync set up, replication rules elect one leader per rule through an S3-CAS lease in the sync bucket (default lease_ttl: "300s", heartbeat_interval: "60s"); a dead leader's lease lapses and a peer takes over automatically. Lifecycle and maintenance jobs still use node-local database leases, so they may run on more than one pod (idempotent — wasteful, not corrupting). Do not scale above one replica if each pod has its own independent /data/deltaglider_config.db — in that shape, each pod is an independent control plane. To run more than one instance, set up config sync first: How to run multiple instances (HA).
Useful values#
| Value | Purpose |
|---|---|
image.repository / image.tag | Container image. Defaults to chart appVersion. |
auth.existingSecret | Secret created outside Helm. Minimum keys: DGP_ACCESS_KEY_ID, DGP_SECRET_ACCESS_KEY, DGP_BOOTSTRAP_PASSWORD_HASH; add DGP_BE_AWS_* for S3 backends. Keeps credentials out of Helm values and release history. |
config.inline | Canonical DeltaGlider YAML rendered into /data/deltaglider_proxy.yaml. |
persistence.* | PVC settings for /data. |
ingress.* | Optional host/TLS routing. |
env / envFrom | Extra non-secret env and env sources. |
backendCredentials.* | Convenience S3 backend env values when the chart creates the Secret. |
networkPolicy.* / autoscaling.* | Optional pod-level network policy / HPA. |
Verify#
kubectl -n dgp rollout status deploy/dgp-deltaglider-proxy
helm test dgp -n dgp # starts a curl pod; fails unless /_/health returns success
Then from outside the cluster:
curl -fsS https://s3.acme.example/_/health
aws --endpoint-url https://s3.acme.example s3 ls
The liveness probe hits GET /_/health (fast, no I/O); the readiness probe hits GET /_/ready, which really probes the storage backend and config DB — a pod that can't reach its backend stays out of rotation. A pod stuck out of Running usually means the PVC didn't bind or the config failed validation — kubectl logs shows the startup error.
Related#
- Kubernetes hello world — the local
kindwalkthrough - How to scale out with the Kubernetes operator — multi-pod deployments
- How to take a proxy to production — the full production checklist
- How to serve TLS — ingress timeouts and forwarded headers
- Configuration reference — every field in
config.inline