Skip to content

Self-hosting on Kubernetes

Install the data plane with Helm. The chart is published to the GHCR OCI registry — no helm repo add needed — and by default it is self-contained: NATS, Redis, ClickHouse, and Postgres run inside the release as StatefulSets, so a bare cluster is enough to start.

For a one-command install, use origamy deploy — it runs this Helm install for you and prompts for endpoint exposure.

  • kubectl connected to your cluster
  • Helm 3.12+ (helm version)
  • Outbound TCP 443 from the origamy-dp namespace to tunnel.origamy.io
  • Your data plane token from the setup wizard (dashboard → Connections)
  1. Create the namespace.

    Terminal window
    kubectl create namespace origamy-dp
  2. Store your token as a Secret.

    Never pass the token via --set — it would land in Helm release history (helm history), readable by anyone with cluster access.

    Terminal window
    kubectl create secret generic origamy-byod-token \
    --namespace origamy-dp \
    --from-literal=auth-token=dpt_<PASTE_YOUR_TOKEN_HERE>
  3. Install the chart.

    Terminal window
    helm install odp oci://ghcr.io/qubelylabs/charts/origamy-data-plane \
    --namespace origamy-dp \
    --version 0.1.12 \
    --set controlPlane.url=tunnel.origamy.io:443 \
    --set controlPlane.dataPlaneId=<YOUR_DATA_PLANE_ID> \
    --set portalAgent.existingSecret=origamy-byod-token

    The secret’s key defaults to auth-token; override with portalAgent.existingSecretAuthKey if you named it differently.

  4. Verify.

    Terminal window
    kubectl get pods -n origamy-dp
    kubectl logs -n origamy-dp deploy/odp-portal-agent --tail=30

    Look for portal-agent connected to control plane. The dashboard’s Connections page updates automatically.

Each bundled datastore can be swapped for an external one by disabling it and pointing at yours, e.g. an existing ClickHouse cluster:

Terminal window
kubectl create secret generic origamy-clickhouse \
--namespace origamy-dp \
--from-literal=clickhouse-password=<YOUR_CLICKHOUSE_PASSWORD>
helm upgrade odp oci://ghcr.io/qubelylabs/charts/origamy-data-plane \
--namespace origamy-dp --reuse-values \
--set clickhouse.enabled=false \
--set clickhouse.host=<YOUR_CLICKHOUSE_HOST> \
--set clickhouse.port=9000 \
--set clickhouse.existingSecret=origamy-clickhouse

The same pattern applies to nats, redis, and postgres (<name>.enabled=false plus the connection settings). Store passwords in Secrets for the same reason as the token.

The predictor is an opt-in service (a gated plan feature, disabled by default). It trains churn, purchase-propensity, and lifetime-value models on your workspace’s own event history and writes the results back as profile traits. Turn it on with:

Terminal window
origamy upgrade --enable-predictor

Use the CLI for this rather than a raw helm upgrade --set predictor.enabled=true: the CLI re-applies your values from a clean export so the chart’s own predictor defaults fill in. A raw --reuse-values --set on a release installed before the predictor existed has none of those defaults and the upgrade fails to render. origamy upgrade --disable-predictor turns it back off.

Everything happens inside your data plane: models are fit against the bundled ClickHouse, stored in the bundled Postgres, and the scores are written back to Redis and ClickHouse. No profile data and no model ever leaves the cluster — the predictor makes no outbound calls.

Once it has run, computed traits appear on each profile — use them anywhere you use traits (segment rules, the People filter, the profile view):

Trait Meaning
churn_score 0–1 probability the profile goes inactive within the horizon
propensity_score 0–1 probability the profile converts
ltv_score predicted lifetime value, in your revenue units
churn_bucket, propensity_bucket low / medium / high band of the matching score

It trains daily and scores hourly. A model only trains once there are enough labeled profiles (default 200); propensity and LTV additionally need to know which events count as a conversion (default: conversion, order completed, order_completed, purchase, checkout completed). On a smaller workspace, lower the threshold and name your conversion events:

Terminal window
origamy upgrade --enable-predictor \
--set predictor.predict.minSamples=20 \
--set 'predictor.predict.conversionEvents=order completed,subscription started'

kubectl logs -n origamy-dp deploy/odp-predictor shows each train/score pass; too few samples there just means the workspace hasn’t accumulated enough history yet.

Your SDKs send events to the ingestion gateway, a ClusterIP service by default — reachable only inside the cluster. To receive events you must expose it.

Option A — LoadBalancer:

Terminal window
helm upgrade odp oci://ghcr.io/qubelylabs/charts/origamy-data-plane \
--version 0.1.12 --namespace origamy-dp --reuse-values \
--set ingestGateway.service.type=LoadBalancer \
--set-string 'ingestGateway.service.annotations.service\.beta\.kubernetes\.io/aws-load-balancer-scheme=internet-facing'
# then get the public address:
kubectl get svc -n origamy-dp odp-ingestion-gateway

Send events to http://<load-balancer-host>:8081/v1/track.

Option B — Ingress (HTTPS on your domain):

Terminal window
helm upgrade odp oci://ghcr.io/qubelylabs/charts/origamy-data-plane \
--version 0.1.12 --namespace origamy-dp --reuse-values \
--set ingestGateway.ingress.enabled=true \
--set ingestGateway.ingress.host=events.yourcompany.com \
--set ingestGateway.ingress.tls.enabled=true \
--set ingestGateway.ingress.tls.secretName=events-tls

Send events to https://events.yourcompany.com/v1/track.

Test it (works before you expose anything):

Terminal window
kubectl port-forward -n origamy-dp svc/odp-ingestion-gateway 8081:8081
curl -u <write-key>: http://localhost:8081/v1/track \
-H 'Content-Type: application/json' -d '{"userId":"test","event":"hello"}'

Once your endpoint is public, paste it into your source’s Setup tab in the dashboard so the copy-paste SDK snippets use it.

NetworkPolicy (if your cluster enforces one)

Section titled “NetworkPolicy (if your cluster enforces one)”
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
name: origamy-portal-agent-egress
namespace: origamy-dp
spec:
podSelector:
matchLabels:
app.kubernetes.io/component: portal-agent
policyTypes: [Egress]
egress:
- ports:
- protocol: TCP
port: 443
Tier Nodes CPU RAM Capacity
Starter 3 2 vCPU each 4 GB each < 100k events/day
Production 5 4 vCPU each 16 GB each 100k – 2M events/day
High volume 8+ 8 vCPU each 32 GB each 2M+ events/day

For high-volume production, run ClickHouse on dedicated nodes — a common shape is 3× (8 vCPU, 32 GB RAM, 500 GB NVMe) — or bring an externally managed cluster (see above).

Rotate the token from the Connections page, patch the Secret, and restart portal-agent:

Terminal window
kubectl create secret generic origamy-byod-token \
--namespace origamy-dp \
--from-literal=auth-token=dpt_<NEW_TOKEN> \
--dry-run=client -o yaml | kubectl apply -f -
kubectl rollout restart deployment/odp-portal-agent -n origamy-dp
kubectl rollout status deployment/odp-portal-agent -n origamy-dp

Use the CLI — it pins every service image to the target version (a raw helm upgrade --reuse-values can silently leave pods on old images) and never touches your datastores:

Terminal window
origamy upgrade # to the latest published version
origamy upgrade --version 0.1.17 # to a specific version
origamy rollback # back to the previous revision

origamy upgrade is also how you switch optional services on or off later: --enable-ai / --disable-ai for the agentic AI engine, and --enable-predictor / --disable-predictor for prediction (see above). Toggles preserve all your existing values.

Symptom Fix
Pod in CrashLoopBackOff Check the Secret exists with the right key: kubectl describe secret origamy-byod-token -n origamy-dp. The key must be auth-token (not AUTH_TOKEN).
dial tcp: i/o timeout to tunnel.origamy.io:443 A NetworkPolicy is blocking egress — apply the example above, or confirm the egress rule includes TCP 443.
Token rejected (Unauthenticated in logs) The Secret may still hold the old token after a rotation. Patch the Secret and rollout-restart portal-agent.
Sources missing on the data plane Check kubectl logs deploy/odp-config-sync -n origamy-dp and verify controlPlane.url is reachable from inside the cluster.
ClickHouse connection refused (external CH) Verify clickhouse.host and clickhouse.port (native protocol 9000, not HTTP 8123), and that the password Secret matches your cluster.