Skip to content

Kubernetes

z4j ships no Helm chart for the brain. A hand-rolled manifest covers the essentials.

apiVersion: apps/v1
kind: Deployment
metadata:
name: z4j
spec:
replicas: 1
# Recreate, not the default. Kubernetes defaults to RollingUpdate, and its
# maxSurge of 25% ROUNDS UP, so even at replicas: 1 it starts a second pod
# before terminating the first. The new pod migrates the database on boot
# while the old one is still writing, which is exactly the mixed-version
# window described under upgrades: the old brain writes change-log envelopes
# without the columns the new one reads, and a fire that lands in that window
# is skipped rather than deferred. "One replica" is a statement about desired
# count, not about how many processes are alive during a rollout.
strategy:
type: Recreate
selector:
matchLabels: { app: z4j }
template:
metadata:
labels: { app: z4j }
spec:
containers:
- name: brain
image: z4jdev/z4j:latest
ports: [{ containerPort: 7700 }]
env:
- name: Z4J_DATABASE_URL
valueFrom: { secretKeyRef: { name: z4j-secrets, key: database-url } }
- name: Z4J_SECRET
valueFrom: { secretKeyRef: { name: z4j-secrets, key: app-secret } }
- name: Z4J_SESSION_SECRET
valueFrom: { secretKeyRef: { name: z4j-secrets, key: session-secret } }
- name: Z4J_AUDIT_CHAIN_SECRET
valueFrom: { secretKeyRef: { name: z4j-secrets, key: audit-chain-secret } }
- name: Z4J_PUBLIC_URL
value: https://z4j.example.com
- name: Z4J_ALLOWED_HOSTS
value: '["z4j.example.com"]'
readinessProbe:
httpGet: { path: /api/v1/health/ready, port: 7700 }
periodSeconds: 10
livenessProbe:
httpGet: { path: /api/v1/health, port: 7700 }
periodSeconds: 30
resources:
requests: { cpu: "200m", memory: "256Mi" }
limits: { cpu: "2", memory: "2Gi" }

Plus a Service + Ingress per your cluster's conventions.

Your Ingress must allow WebSocket upgrades. Example (nginx-ingress):

metadata:
annotations:
nginx.ingress.kubernetes.io/proxy-read-timeout: "3600"
nginx.ingress.kubernetes.io/proxy-send-timeout: "3600"

Running more than one brain replica requires sticky session routing for the /ws endpoint (each agent pins to one brain pod). z4j provides no affinity-balance helper of its own, so multi-replica deploys rely on the load balancer's own session-affinity setting.

Use a managed Postgres (Cloud SQL, RDS, Crunchy) or an operator (Zalando, CNPG). Do not run Postgres in a StatefulSet with local storage unless you really know what you're doing.

Inject Z4J_*_SECRET via Secret objects or external managers (Vault, AWS Secrets Manager, GCP Secret Manager). Do not hard-code.

  • Scrape /metrics with Prometheus (Bearer Z4J_METRICS_AUTH_TOKEN, or set Z4J_METRICS_PUBLIC=1 if the port is firewalled).
  • Ship stdout JSON logs with Fluent Bit / Vector.