Skip to content

Deploy to Kubernetes

Talmi is a single static binary in a distroless image. This guide runs it in Kubernetes with its config from a ConfigMap, secrets from a Secret, PostgreSQL for persistence, and TLS at the edge.

Kubernetes support is work in progress

There is no official Helm chart or supported manifest set yet. The examples below are a starting point, not a supported deployment. In particular, running more than one replica is not properly supported (see Multiple replicas). Treat this page as guidance for a single-replica setup while proper support is built out.

Before you begin

  • A Kubernetes cluster and kubectl access.
  • A container registry reachable from the cluster (the image is published to ghcr.io/darmiel/talmi).
  • A PostgreSQL database if you want persistence. See Run with PostgreSQL.
  • Your bootstrap config and sourced tree ready, and credentials as secret material.

Run the container

The image runs as a non-root user. Use the server run subcommand and point --config at the bootstrap file:

docker run --rm -p 8080:8080 \
  -v /etc/talmi:/etc/talmi:ro \
  ghcr.io/darmiel/talmi:latest server run --addr :8080 --config /etc/talmi/talmi.yaml

The command is server run

Run Talmi with server run --addr ... --config .... There is no serve command.

Deployment and Service

Mount the bootstrap file and the sourced tree from a ConfigMap, and credentials from a Secret, referenced in config with file:/env:. Probe /healthz for liveness and readiness.

apiVersion: apps/v1
kind: Deployment
metadata:
  name: talmi
spec:
  replicas: 1
  selector: { matchLabels: { app: talmi } }
  template:
    metadata: { labels: { app: talmi } }
    spec:
      containers:
        - name: talmi
          image: ghcr.io/darmiel/talmi:latest
          args: ["server", "run", "--addr", ":8080", "--config", "/etc/talmi/talmi.yaml"]
          ports: [ { containerPort: 8080 } ]
          envFrom:
            - secretRef: { name: talmi-secrets }   # DSNs, tokens as env: refs
          volumeMounts:
            - { name: config, mountPath: /etc/talmi, readOnly: true }
            - { name: keys,   mountPath: /run/secrets, readOnly: true }
          livenessProbe:  { httpGet: { path: /healthz, port: 8080 } }
          readinessProbe: { httpGet: { path: /healthz, port: 8080 } }
      volumes:
        - { name: config, configMap: { name: talmi-config } }
        - { name: keys,   secret: { secretName: talmi-keys } }

Put a TLS-terminating ingress or load balancer in front. Talmi speaks plaintext HTTP and expects TLS at the edge.

Behind an ingress

The ingress is the TCP peer Talmi sees, so client-IP-based rate limiting must be told which peers to trust. Set rate_limit.trusted_proxies to your ingress/pod CIDRs so Talmi reads the real client from X-Forwarded-For. See Configure rate limiting.

Persistence and migrations

Use the postgres store and auditor for anything beyond ephemeral runs. Talmi does not migrate the database; run migrations as a Job before rolling out a version that needs a newer schema:

apiVersion: batch/v1
kind: Job
metadata:
  name: talmi-migrate
spec:
  template:
    spec:
      restartPolicy: Never
      containers:
        - name: migrate
          image: migrate/migrate:latest
          args: [ "-path", "/migrations", "-database", "$(TALMI_STORE_DSN)", "up" ]
          envFrom: [ { secretRef: { name: talmi-secrets } } ]
          volumeMounts: [ { name: migrations, mountPath: /migrations, readOnly: true } ]
      volumes:
        - { name: migrations, configMap: { name: talmi-migrations } }

See Run with PostgreSQL for the full store and audit setup. With Helm, run this Job as a pre-install / pre-upgrade hook so the schema is applied before the new pods start.

Multiple replicas

Run a single replica for now

More than one replica is not properly supported yet. Scheduled tasks run on every replica (so they fire N times), and a config webhook only reaches the one pod it hits (the others reload on their sync.interval). Coordinated multi-node operation is on the roadmap.

Until multi-node lands, prefer a single replica. If you must scale out, at least use the redis rate-limit backend so all replicas share one view (see Configure rate limiting), and accept the duplicated background work and webhook lag above.

Verify

kubectl rollout status deploy/talmi
kubectl port-forward deploy/talmi 8080:8080 &
curl -sS localhost:8080/healthz

Troubleshooting

  • CrashLoopBackOff at startup: usually a config error or an unreachable database. Check kubectl logs; the server fails closed rather than starting misconfigured.
  • all clients share one rate-limit bucket: trusted_proxies is unset, so every request looks like it came from the ingress IP. Set it to the ingress CIDR.
  • sessions break after a rollout: the signing key is not stable across pods. Mount one signing.key from a Secret, not the --dev ephemeral key.

Next steps