56 docs indexed

Self-Hosting

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):

StoreRoleLose it and…
PostgresConfig source of truth (tenants, keys, models, audit)you lose all configuration
ClickHouseUsage / cost ledgeryou lose usage history
RedisHot cache + token budgetsnothing 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.

Choosing a scenario

ScenarioWho manages the dataSurvives restart?Use for
Persistent (PVC)the chart, on PVCsYesSelf-hosted single cluster, no external DBs yet
Ephemeralthe chart, in memoryNo — data is lostDemos, CI, kicking the tires
Externalyou (managed / Docker / operator)Managed by youProduction

Ready-made values files for each live in deploy/k8s/obleth/examples/.


Scenario 1 — bundled datastores with PVCs

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).


Scenario 2 — bundled datastores without persistence (test only)

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.


Scenario 3 — external datastores

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:

  • Managed cloud — RDS/Cloud SQL (Postgres), ElastiCache/MemoryStore (Redis), ClickHouse Cloud / Altinity.
  • Kubernetes operatorsCloudNativePG for Postgres, the ClickHouse operator, Redis Sentinel/cluster charts.
  • Docker — the standalone stack below.

Hosting external datastores in Docker

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.

1. Start the stack

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
ServicePortNotes
Postgres5432config source of truth
Redis6379AOF persistence (appendfsync everysec)
ClickHouse8123 (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.

2. Point obleth at them

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.


After install

Helm and the binary bring services up but do not register models or create tenant keys. Continue with: