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-planeProduct.
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=registryCONTROL_REGISTRY_URL, pointing at the control plane's/internal/registryINTERNAL_API_KEY, the shared Cue/control-plane service credential- a credential-less, database-less
MONGO_DB_URIcontaining only Mongo hosts/options - blank
MINIO_ACCESS_KEYandMINIO_SECRET_KEY TENANT_SECRETS_ENABLED=trueTENANT_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:
- create the Keycloak Organization;
- create the tenant-scoped Mongo user;
- create the tenant's MinIO bucket and scoped access key;
- call Cue's
/internal/admin/migrateendpoint; - 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:
- choose a tenant slug and move or register the Cue database/bucket under that descriptor;
- create the Keycloak Organization and add existing users;
- provision scoped Mongo/MinIO credentials;
- 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.