56 docs indexed
Run obleth on your own infrastructure: storage scenarios for the Helm chart and how to host Postgres, Redis, and ClickHouse yourself in Docker.
obleth is built to be self-hosted. This guide covers the decisions that matter once you move past a throwaway demo: where your data lives and who manages the datastores.
obleth needs three backing stores (see Datastores):
| Store | Role | Lose it and… |
|---|---|---|
| Postgres | Config source of truth (tenants, keys, models, audit) | you lose all configuration |
| ClickHouse | Usage / cost ledger | you lose usage history |
| Redis | Hot cache + token budgets | nothing permanent — obleth re-warms from Postgres |
How you host those three stores is the single biggest self-hosting decision. The
Helm chart in deploy/k8s/obleth/ supports three storage scenarios.
| Scenario | Who manages the data | Survives restart? | Use for |
|---|---|---|---|
| Persistent (PVC) | the chart, on PVCs | Yes | Self-hosted single cluster, no external DBs yet |
| Ephemeral | the chart, in memory | No — data is lost | Demos, CI, kicking the tires |
| External | you (managed / Docker / operator) | Managed by you | Production |
Ready-made values files for each live in
deploy/k8s/obleth/examples/.
The chart runs its own Postgres, Redis, and ClickHouse and stores their data on PersistentVolumeClaims. Data survives pod restarts, reschedules, and node drains. This is the recommended starting point when you don't yet have managed datastores.
Each datastore has a persistence block (enabled by default):
postgres:
enabled: true
persistence:
enabled: true
size: 10Gi
storageClass: "" # "" = cluster default StorageClass; set a name to pin one
accessMode: ReadWriteOnce
redis:
persistence: { enabled: true, size: 1Gi }
clickhouse:
persistence: { enabled: true, size: 20Gi }
The datastore Deployments use the Recreate rollout strategy so a new pod never
tries to attach the same ReadWriteOnce PVC while the old pod still holds it.
Requirements: a StorageClass must exist in the cluster. Check with:
kubectl get storageclass
Most managed clusters (EKS/GKE/AKS), k3s, and kind ship a default. If none is
marked (default), set persistence.storageClass explicitly.
Install:
helm install obleth deploy/k8s/obleth -n obleth --create-namespace \
-f deploy/k8s/obleth/examples/values-persistent.yaml \
--set obleth.adminToken="$(openssl rand -hex 32)" \
--set obleth.encryptionKey="$(openssl rand -base64 32)" \
--set postgres.password="$(openssl rand -hex 16)" \
--set clickhouse.password="$(openssl rand -hex 16)" \
--set controlPlane.dashboardPassword="$(openssl rand -hex 16)" \
--set controlPlane.dashboardSessionSecret="$(openssl rand -hex 32)"
Note:
Single-replica Deployments with a PVC are fine for self-hosting, but they are not highly available — during a node failure the datastore is down until the pod reschedules. For HA, move to the External scenario with an operator (CloudNativePG, the ClickHouse operator, Redis Sentinel/cluster).
Set persistence.enabled: false and the datastores use emptyDir volumes.
Note:
All data is lost when a datastore pod restarts. That includes every tenant,
API key, registered model, audit entry (Postgres) and all usage history
(ClickHouse). A helm upgrade that rolls the pods, a node drain, or an OOM kill
all wipe the data. Use this only for throwaway demos, CI, or a cluster with no
StorageClass.
postgres: { persistence: { enabled: false } }
redis: { persistence: { enabled: false } }
clickhouse: { persistence: { enabled: false } }
helm install obleth deploy/k8s/obleth -n obleth --create-namespace \
-f deploy/k8s/obleth/examples/values-ephemeral.yaml \
--set obleth.adminToken="$(openssl rand -hex 32)" \
--set postgres.password=devpassword \
--set clickhouse.password=devpassword \
--set controlPlane.dashboardPassword=devpassword \
--set controlPlane.dashboardSessionSecret=dev-session-secret-at-least-32-chars
This profile keeps the GPU-free benchmark fixture backend enabled, so inference calls succeed without a real upstream.
obleth runs only its stateless workloads (data plane + control plane) and connects to Postgres, Redis, and ClickHouse you operate yourself. This is the recommended production topology — stateful systems get purpose-built backups, HA, and point-in-time recovery instead of plain Deployments.
postgres:
enabled: false
external:
url: "postgres://obleth:STRONGPASSWORD@my-pg-host:5432/obleth"
redis:
enabled: false
external:
url: "redis://my-redis-host:6379"
clickhouse:
enabled: false
# obleth authenticates to ClickHouse with these even when external:
user: obleth
password: "STRONGPASSWORD"
db: obleth
external:
url: "http://my-clickhouse-host:8123"
benchmarkBackend:
enabled: false
obleth:
upstreamBaseUrl: "https://my-vllm-or-aibrix-endpoint/v1"
When a store is enabled: false, its external.url is required — the chart
fails to render with a clear message if it is missing, so obleth never boots
half-configured.
helm install obleth deploy/k8s/obleth -n obleth --create-namespace \
-f deploy/k8s/obleth/examples/values-external.yaml \
--set obleth.adminToken="$(openssl rand -hex 32)" \
--set obleth.encryptionKey="$(openssl rand -base64 32)"
Where to get those external stores:
If you want external datastores without a managed service or a database operator,
the repo ships a dedicated Compose stack tuned to be a durable backing tier
(named volumes, healthchecks, restart: unless-stopped, host-published ports):
deploy/docker/datastores.compose.yml.
This is separate from the all-in-one docker-compose.yml — it runs only the
three datastores so that an obleth elsewhere (a Helm install with
*.enabled: false, or a bare-metal binary) can connect to them.
cd deploy/docker
cp datastores.env.example datastores.env # then edit the passwords
docker compose --env-file datastores.env -f datastores.compose.yml up -d
datastores.env controls credentials and the bind address:
# Loopback only by default — change to a private interface IP to allow an
# off-box obleth, and protect it with a firewall / private network.
BIND_ADDR=127.0.0.1
POSTGRES_USER=obleth
POSTGRES_PASSWORD=change-me-postgres
POSTGRES_DB=obleth
CLICKHOUSE_USER=obleth
CLICKHOUSE_PASSWORD=change-me-clickhouse
CLICKHOUSE_DB=obleth
| Service | Port | Notes |
|---|---|---|
| Postgres | 5432 | config source of truth |
| Redis | 6379 | AOF persistence (appendfsync everysec) |
| ClickHouse | 8123 (HTTP), 9000 (native) | obleth uses the HTTP port |
Data persists in the pgdata, redisdata, and chdata named volumes across
docker compose down/up. (docker compose down -v deletes them.)
Note:
ClickHouse's built-in default user is restricted to localhost, so the stack
provisions a network-accessible obleth user via CLICKHOUSE_USER /
CLICKHOUSE_PASSWORD. Use those credentials from obleth, not default.
Bare-metal binary (OBLETH_* env vars). Replace DB_HOST with the Docker
host's reachable address:
export OBLETH_DATABASE_URL="postgres://obleth:change-me-postgres@DB_HOST:5432/obleth"
export OBLETH_REDIS_URL="redis://DB_HOST:6379"
export OBLETH_CLICKHOUSE_URL="http://DB_HOST:8123"
export OBLETH_CLICKHOUSE_USER=obleth
export OBLETH_CLICKHOUSE_PASSWORD=change-me-clickhouse
export OBLETH_CLICKHOUSE_DB=obleth
export OBLETH_ADMIN_TOKEN="$(openssl rand -hex 32)"
./target/release/obleth-proxy
Helm (values-external.yaml):
postgres:
enabled: false
external: { url: "postgres://obleth:change-me-postgres@DB_HOST:5432/obleth" }
redis:
enabled: false
external: { url: "redis://DB_HOST:6379" }
clickhouse:
enabled: false
user: obleth
password: "change-me-clickhouse"
db: obleth
external: { url: "http://DB_HOST:8123" }
obleth applies its embedded schema on first boot — there is no separate migration step. It connects, creates tables idempotently, and starts serving.
Note:
The datastore ports bind to 127.0.0.1 by default so they are not exposed to
your network. Only widen BIND_ADDR to a private interface and put the ports
behind a firewall / private network. Never expose Postgres, Redis, or
ClickHouse to the public internet.
Helm and the binary bring services up but do not register models or create tenant keys. Continue with:
sk_... key