Skip to content

Quickstart — self-hosted cell

Bring up a single OpenDome cell that serves the OSL API, with no control-plane and no phone-home. This is the path a self-hoster takes; the OpenDome-operated product layers the central control-plane and console on top of the exact same charts.

Everything below comes from the public opendome.eu/platform repository — clone it and run the commands from its root. The engineering-depth version of this walk is docs/quickstart-oss.md.

  • A Kubernetes cluster (kind works) with:
    • A CNI that enforces NetworkPolicy — Cilium recommended. kind’s default kindnet does not enforce it. With Cilium, install it with --set 'policyCIDRMatchMode={nodes}'; without it, Cilium ignores ipBlock rules covering node/apiserver IPs and the bundled Postgres dead-locks at “Setting up primary”. (scripts/cluster-create.sh does this for you on kind.)
    • An Ingress controller to reach the OSL API, e.g. helm install ingress-nginx ingress-nginx/ingress-nginx -n ingress-nginx --create-namespace --version 4.11.3. OSL identity is carried only by Authorization: Bearer <jwt> — no trusted identity header, no ingress header-rewrite configuration needed.
    • A default StorageClass (for the bundled object store + Postgres PVCs).
    • The CloudNativePG operator cluster-wide — only if you use the bundled Postgres: helm install cnpg cnpg/cloudnative-pg -n cnpg-system --create-namespace --version 0.27.1.
  • helm ≥ 3.12 and kubectl.
  1. The cell shell + bundled infra (object store + Postgres).

    standard-tenant creates the namespace, a deny-all NetworkPolicy (no control-plane allow rules), quotas, and — in the standalone profile — an in-namespace object store (RustFS) and CloudNativePG Postgres, plus the tenant-object-storage Secret.

    Terminal window
    # The deny-all NetworkPolicy only lets pods reach the Kubernetes API through
    # this allowlist (CNPG's init needs it). Derive both addresses from YOUR
    # cluster — no per-distro guessing:
    APISERVER_VIP=$(kubectl get svc kubernetes -o jsonpath='{.spec.clusterIP}')
    APISERVER_EP=$(kubectl get endpoints kubernetes -o jsonpath='{.subsets[0].addresses[0].ip}')
    helm install demo charts/standard-tenant \
    -n tenant-demo --create-namespace \
    --set tenant=demo \
    --set "apiServerEgress.cidrs[0]=$APISERVER_VIP/32" \
    --set "apiServerEgress.endpointCidrs[0]=$APISERVER_EP/32"

    With no explicit values the chart mints random credentials on first install and reuses them on upgrades. Recover them any time:

    Terminal window
    kubectl -n tenant-demo get secret tenant-object-storage -o jsonpath='{.data.accessKey}' | base64 -d
    kubectl -n tenant-demo get secret postgres-postgresql -o jsonpath='{.data.password}' | base64 -d

    Prefer your own S3 / Postgres? Flip objectStore.bundled=false / postgres.bundled=false and supply credentials — the bundled RustFS is pre-GA and intended for dev/standalone; use a GA S3 for production. See Charts & topologies.

  2. The OSL engine — the consumption surface.

    Terminal window
    helm install osl charts/tenant-semantic-api \
    -n tenant-demo \
    --set tenant.id=demo \
    --set ingress.host=osl.tenant-demo.127.0.0.1.nip.io

    Governed reads accept identity only from a verified Authorization: Bearer <jwt> (an ActorToken v2). Headers like X-Actor-Roles are always ignored — there is no header-trust switch. The standalone chart provides an HS256 signing Secret; a production self-hoster can configure a verifying key for their own OIDC issuer instead.

  3. Verify the cell is usable via its API — no control-plane.

    Terminal window
    HOST=osl.tenant-demo.127.0.0.1.nip.io
    curl -fsS "http://$HOST/health" # {"status":"ok",...}
    curl -fsS "http://$HOST/osl/conformance" # engine + declared conformance
    curl -fsS "http://$HOST/osl/schema" # compiled manifest (empty until you author facets)
  4. Mint a token and make a governed call.

    scripts/osl-token.sh reads the chart-generated HS256 Secret via kubectl and signs an ActorToken v2 (defaults: sub=local-admin, acl_roles=analyst, TTL 300 s):

    Terminal window
    TOKEN=$(scripts/osl-token.sh demo)
    # The single consumption endpoint. This structural example requires the
    # referenced model and a matching subject grant in the manifest/policy:
    curl -fsS "http://$HOST/osl/query" \
    -H "Authorization: Bearer $TOKEN" \
    -H 'Content-Type: application/json' \
    -d '{"osql":"SELECT customer_id, name FROM osl.entities.customer LIMIT 5"}'
    # RETRIEVE is another form of the same endpoint, not a separate route:
    curl -fsS "http://$HOST/osl/query" \
    -H "Authorization: Bearer $TOKEN" \
    -H 'Content-Type: application/json' \
    -d '{"osql":"SELECT text, source FROM RETRIEVE(facet => '\''call_transcripts'\'', query => '\''renewal risk'\'', top_k => 5)"}'
  5. Connectors + orchestration — the management cell. (optional)

    To ingest (sync a source → land → curate) add tenant-mgmt-api in dagster mode: the cell owns connectors/runs and an in-namespace Dagster daemon schedules and launches the pipeline Jobs. Requires the structured leg (charts/lakehouse) before driving a sync.

    Terminal window
    helm install mgmt charts/tenant-mgmt-api \
    -n tenant-demo \
    --set tenantId=demo \
    --set runLauncher.mode=dagster \
    --set dagster.enabled=true

    See Orchestration (in-cell Dagster) for how runs fire, and keep the management API private behind your own administrative gateway — it is operational control, not an OSL data surface.

If the health/metadata calls return 200 with no opendome-system namespace present, the cell is standalone-usable. The query examples become runnable after loading the named models/facets and granting the token subject. Run scripts/oss-gate.sh to assert the installation end-to-end.

  • Structured leg (Trino + Nessie + Iceberg) — charts/lakehouse. Defaults to in-namespace endpoints with credentials from the tenant-object-storage Secret; needs the bundled/external Postgres from step 1.
  • Config API (allowlist / pipeline / discovery) — charts/tenant-config. Already standalone: uses the cell’s own Postgres.

Released images are pinned by immutable digest and signed keyless with the GitLab CI OIDC identity. Verify before deploying:

Terminal window
IMAGE=registry.gitlab.com/opendome.eu/platform/tenant-semantic-api
DIGEST=$(git show <tag>:charts/tenant-semantic-api/values.yaml | yq '.image.digest')
cosign verify "${IMAGE}@${DIGEST}" \
--certificate-oidc-issuer "https://gitlab.com" \
--certificate-identity-regexp "^https://gitlab.com/opendome.eu/platform//.gitlab-ci.yml@refs/(heads/main|tags/.*)$"

The full procedure is on the Security posture page.

Released charts pin images by immutable digest (image.digest: sha256:…), which takes precedence over the tag. That digest identifies OpenDome’s published image — it is meaningless in any other registry. If you rebuild the cell images into your own registry, clear the shipped digest or every pod will ImagePullBackOff:

Terminal window
helm install osl charts/tenant-semantic-api \
--set image.repository=myreg.example/tenant-semantic-api \
--set image.digest="" # ← REQUIRED when you change the repository

Do the same for tenant-mgmt-api.runnerImages.* (pinned to repo:<ver>@sha256:…).

To serve real retrievals: create the bucket (tenant-demo) in your object store, author OSL facets (UnstructuredFacet / JointEntity YAML under the engine’s manifest.facets), and ingest Lance datasets — the documents pipeline does this for you from real sources. See Concepts for the manifest model and the API reference for how consumption is called.