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 managed/Enterprise product layers the central control-plane and console on top of the exact same charts.

  • A Kubernetes cluster (kind works) with:
    • A CNI that enforces NetworkPolicyCilium 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.
    • A default StorageClass (for the bundled object store + Postgres PVCs).
    • The CloudNativePG operator installed cluster-wide — only if you use the bundled Postgres.
  • helm ≥ 3.12 and kubectl.

For ingress-nginx, install it stripping any client-supplied X-Actor-Roles at the edge (defense in depth — the engine ignores the header in standalone anyway):

Ventana de terminal
helm install ingress-nginx ingress-nginx/ingress-nginx \
-n ingress-nginx --create-namespace --version 4.11.3 \
--set-string 'controller.proxySetHeaders.X-Actor-Roles='

If you use the bundled Postgres, install CloudNativePG first:

Ventana de terminal
helm install cnpg cnpg/cloudnative-pg \
-n cnpg-system --create-namespace --version 0.27.1
  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.

    Ventana de terminal
    # 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:

    Ventana de terminal
    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
  2. The OSL engine — the consumption surface.

    Ventana de terminal
    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

    This enables the opt-in Ingress and renders the OSL manifest locally from values. In the standalone profile the engine does not trust the X-Actor-Roles header — a public Ingress caller cannot spoof roles. ACL’d access goes through the local token issuer: the chart generates a signing Secret and the engine validates Authorization: Bearer HS256 tokens.

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

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

    The token’s roles drive the ACL — facets honor them server-side.

    Ventana de terminal
    TOKEN=$(scripts/osl-token.sh demo --roles analyst)
    curl -fsS -H "Authorization: Bearer $TOKEN" "http://$HOST/osl/schema"

If those return 200 with no opendome-system namespace present, the cell is standalone-usable. Run scripts/oss-gate.sh to assert this 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 + a direct GET/PUT /config/{key} API.
  • Ingest / orchestrationcharts/dagster. Defaults: no control-plane callbacks, no upstream telemetry, Jobs run in this namespace.

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:

Ventana de terminal
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.*. If you pull OpenDome’s published images, leave the digest as shipped and cosign verify it.

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 to s3://tenant-demo/lance. See Concepts for the manifest model and the API reference for how retrieval is called.