Skip to content

Tenancy

Product boundary

Tenancy applies to Cue, not Circles.

  • Cue can serve one configured tenant (static) or a pool of control-plane tenants (registry). Every Cue request, database access, object-storage access, worker job, and local role is scoped to the active tenant.
  • Circles is global by design. One installation has one public community and one MongoDB database containing all approved circles shown on the unauthenticated start page. It never asks the control plane for tenants, never checks Keycloak Organization membership, never switches databases, never sends or consumes X-Tenant-Id, and is not a control-plane Product.

Circles does not depend on shared/vacay-tenant. It uses its normal application-wide Mongo configuration, and its roles and approval state are installation-wide.

Cue modes

vacay.tenant.mode selects where Cue gets its tenant descriptor:

Mode Tenant source Use case
static (default) One descriptor from vacay.tenant.static-tenant.* Existing single-customer Cue deployments and local development
registry Control-plane registry, polled and cached Pooled Cue deployment

Both modes use the same request and data-access path. This keeps static deployments representative of production tenant isolation without requiring a control plane.

Cue static mode

Defaults preserve the pre-tenancy resource names:

Setting Environment variable Default
vacay.tenant.mode VACAY_TENANT_MODE static
static-tenant.tenant-id VACAY_TENANT_ID default
static-tenant.mongo-database VACAY_TENANT_MONGO_DB database from MONGO_DB_URI (cue)
static-tenant.bucket-prefix VACAY_TENANT_BUCKET_PREFIX empty, so MINIO_BUCKET stays transcriptions
static-tenant.keycloak-realm KEYCLOAK_REALM vacay
static-tenant.admin-emails VACAY_TENANT_ADMIN_EMAILS empty; comma-separated bootstrap app admins

The Host resolves to the one descriptor, so no X-Tenant-Id is needed. Keycloak Organization membership is not enforced. Cue runs its Mongock changelog and creates its bucket at startup.

Circles separately uses MONGO_DB_NAME/MONGO_DB_URI for its one global database and CIRCLES_ADMIN_EMAILS for comma-separated bootstrap administrators. It has no tenancy or registry configuration.

Cue registry mode

Set:

  • VACAY_TENANT_MODE=registry
  • CONTROL_REGISTRY_URL, pointing at the control plane's /internal/registry
  • INTERNAL_API_KEY, the shared Cue/control-plane service credential
  • a credential-less, database-less MONGO_DB_URI containing only Mongo hosts/options
  • blank MINIO_ACCESS_KEY and MINIO_SECRET_KEY
  • TENANT_SECRETS_ENABLED=true
  • TENANT_SECRETS_REQUIRED=true

Production refuses registry mode unless scoped secrets are both enabled and required. The control plane creates one database-scoped Mongo credential and one bucket-scoped MinIO credential per Cue tenant; applications fetch these over the authenticated internal registry channel. A tenant cannot fall back to a Mongo root user or global MinIO credentials, and pooled Cue processes do not carry either credential.

The active tenant is selected by X-Tenant-Id from the Cue SPA, tenantId on WebSocket setup, or the request Host. The JWT must contain membership in the corresponding Keycloak Organization. Unknown, suspended, or stale tenants are rejected before business code runs.

Registry snapshots and scoped secrets have bounded stale lifetimes. A temporary control-plane outage may use a recently validated cache, but after max-snapshot-age/max-stale-age the service fails closed with 503 rather than serving a tenant whose suspension or credential rotation might have been missed.

Provisioning

The control plane provisions Cue tenants only, in this order:

  1. create the Keycloak Organization;
  2. create the tenant-scoped Mongo user;
  3. create the tenant's MinIO bucket and scoped access key;
  4. call Cue's /internal/admin/migrate endpoint;
  5. mark the tenant active and publish a new registry revision.

Steps are idempotent, audited, retryable, and protected by a renewable provisioning lease. Failed or interrupted work stays non-active. Circles has no provisioning step and no internal tenant-init endpoint.

Workers

Cue jobs carry tenant_id, exact-object presigned GET/PUT URLs, and an opaque per-run task capability. Workers have no MinIO account and never receive the task-signing key. Every callback must echo both X-Tenant-Id and X-Worker-Task-Token; the backend verifies the token is bound to the tenant, resource, operation, and current retry generation.

Upgrading an existing installation

For an existing single-tenant deployment, leave Cue in static mode. The legacy Mongo database, MinIO bucket, realm, and Circles database remain unchanged; no data move is required. Set VACAY_TENANT_ADMIN_EMAILS for Cue and CIRCLES_ADMIN_EMAILS for Circles to at least one operator address if the corresponding app has no local administrator.

Moving Cue into a pooled deployment is a separate, explicit migration:

  1. choose a tenant slug and move or register the Cue database/bucket under that descriptor;
  2. create the Keycloak Organization and add existing users;
  3. provision scoped Mongo/MinIO credentials;
  4. switch Cue to registry mode and verify tenant headers and organization claims.

Circles is not part of this migration. It remains one global installation before and after Cue is pooled.

The control plane must receive CUE_FRONTEND_URL as the public HTTPS base URL of the shared Cue SPA. Tenant-admin invitation links use this value as their post-action redirect.