For the complete documentation index, see llms.txt.
Skip to main content
Version: 8.10 (unreleased)

Storage isolation for Physical Tenants

Each Physical Tenant can use isolated secondary storage, ensuring complete structural separation of process data. This page covers configuration options per backend type.

Related pages

RDBMS storage

Each Physical Tenant can have its own schema or database instance.

Configuration models

Separate schema (recommended for cost-efficiency):

camunda:
physical-tenants:
default:
data:
secondary-storage:
rdbms:
url: jdbc:postgresql://db.example.com:5432/camunda?currentSchema=default_schema
tenanta:
data:
secondary-storage:
rdbms:
url: jdbc:postgresql://db.example.com:5432/camunda?currentSchema=tenant_a_schema
# The 'default_schema' and 'tenant_a_schema' schemas must exist before startup

Separate database instance (maximum isolation):

tenanta:
data:
secondary-storage:
rdbms:
url: jdbc:postgresql://db-tenant-a.example.com:5432/camunda

Mixed vendors: Different Physical Tenants can use PostgreSQL, MySQL, Oracle, etc. in the same cluster.

Validation and operations

  • Configuration: Misconfiguration (duplicate schema/URL) causes a startup error with a clear message. For Oracle, schema isolation uses distinct authenticated users rather than URL differences; a known false positive startup conflict may be reported for identical Oracle URLs in the current alpha release.
  • Pre-startup: Ensure each tenant's schema exists, is empty, and has valid credentials
  • Manual DDL: If running Liquibase scripts separately, apply to every tenant's schema before each upgrade
  • Resource scaling: Each tenant gets its own JDBC datasource per cluster node; add memory/CPU for many tenants
Table prefix must be uppercase

RDBMS table prefixes must use uppercase characters. A lowercase prefix causes Liquibase migration to fail at startup. For example, use TENANTA_ not tenanta_. See camunda/camunda#56093.

Oracle limitation in 8.10 alpha

In the 8.10 alpha release, Oracle supports isolation by table prefix only. Using separate schemas from the same Oracle instance for multiple Physical Tenants is not supported in alpha and will be fixed in a later release.

Elasticsearch/OpenSearch storage

note

Elasticsearch/OpenSearch storage isolation is not yet available in the current alpha release. This section documents the planned configuration.

Use separate clusters or a shared cluster with per-tenant index prefixes.

Naming and collision prevention

  • Prefix format: {tenantId} (dash automatically appended by the application)
  • Collision prevention: Use the full tenant ID; avoid prefixes that are identical to another tenant's prefix. Overlapping prefixes (for example, eu and eu-west) are not caught by startup validation — only exact duplicates fail at startup.
  • Validation: Cluster fails at startup if two tenants have identical index prefixes

Document Store storage

Store documents globally with per-tenant subpaths, or use dedicated stores per tenant. Camunda validates the resulting layout at startup and refuses to start if two tenants would read and write into the same storage.

Configuration models

What Camunda compares. Three properties. bucket-name and endpoint form the namespace, and bucket-path becomes the key prefix. Bucket names are compared case-insensitively, endpoints by scheme, host, port, and path only, and bucket paths case-sensitively after being coerced to end in /.

Every other AWS property is ignored: region, because S3 bucket names are globally unique across regions, and bucket-ttl, force-path-style, chunked-encoding-enabled, and support-legacy-md5, because none of them change which objects a store reads and writes.

Global store with per-tenant subpaths (recommended):

camunda:
document:
default-store-id: shared-s3
aws:
shared-s3:
bucket-name: "camunda-documents"
physical-tenants:
default:
document:
assigned: [shared-s3]
aws:
shared-s3:
bucket-path: "default"
tenanta:
document:
assigned: [shared-s3]
aws:
shared-s3:
# Sibling paths. Neither tenant's path may be nested inside the other's,
# and neither tenant may leave the path unset to use the bucket root.
bucket-path: "tenant-a"

Dedicated store per tenant. Distinct buckets are distinct namespaces, so bucket-path is optional here:

camunda:
physical-tenants:
default:
document:
assigned: [default-s3]
default-store-id: default-s3
aws:
default-s3:
bucket-name: "camunda-documents-default"
tenanta:
document:
assigned: [tenant-a-s3]
default-store-id: tenant-a-s3
aws:
tenant-a-s3:
bucket-name: "camunda-documents-tenant-a"

Startup outcomes.

Tenant ATenant BOutcome
bucket-path: tenant-abucket-path: tenant-bAccepted. Sibling prefixes.
bucket-path unsetbucket-path: tenant-bRejected. The bucket root is a prefix of every key in the bucket.
bucket-path: tenant-abucket-path: tenant-a/nestedRejected. One prefix is nested inside the other.
bucket-path: tenantbucket-path: tenant-b-Accepted. Both are coerced to end in /, so neither encloses the other.
bucket-path: Tenant-A/bucket-path: tenant-a/Accepted. S3 keys are case-sensitive.
region: us-east-1region: eu-west-1, rest identicalRejected. Region isn't part of the location.
endpoint: https://minio-aendpoint: https://minio-b, same bucket and pathAccepted. Different endpoints are different namespaces.
endpoint: https://MINIO/endpoint: https://minio, same bucket and pathRejected. Host case and trailing slashes are ignored.

Combine providers and per-tenant overrides

A tenant's assigned stores don't have to share a provider, and a global store can be combined with a tenant-specific one. Every store still has to satisfy the comparison rules for its own provider.

Hybrid — a global default store plus a tenant-specific store:

camunda:
document:
aws:
default-s3:
bucket-name: "camunda-documents-default"
physical-tenants:
default:
document:
assigned: [default-s3]
default-store-id: default-s3
aws:
default-s3:
# Required. Without a path, this tenant owns the bucket root, which
# encloses every other tenant's path in the same bucket.
bucket-path: "default"
tenanta:
document:
assigned: [default-s3, tenant-a-compliance]
default-store-id: tenant-a-compliance
aws:
tenant-a-compliance:
bucket-name: "camunda-documents-tenant-a-compliance"
default-s3:
bucket-path: "tenant-a"

Mixed providers — a shared GCP store for both tenants, plus an Azure store for one of them:

camunda:
document:
gcp:
default-gcs:
bucket-name: "camunda-documents-default"
physical-tenants:
default:
document:
assigned: [default-gcs]
default-store-id: default-gcs
gcp:
default-gcs:
prefix: "default/"
tenanta:
document:
assigned: [default-gcs, tenant-a-blob]
default-store-id: tenant-a-blob
gcp:
default-gcs:
prefix: "tenant-a/"
azure:
tenant-a-blob:
container-name: "camunda-documents-tenant-a"

Overlap is only ever reported between two different tenants. One tenant may spread its documents across several stores whose prefixes overlap, because reaching its own documents isn't a leak.

Availability and validation

  • At startup: Warning if bucket is missing or credentials are invalid; cluster continues
  • At runtime: An error is returned when a tenant tries to create/retrieve a document if the store is unavailable
  • Validation: The cluster fails to start if two Physical Tenants resolve to overlapping document store locations. See Compare document store locations across tenants
  • Subpath structure: Each tenant writes to the prefix you configure, such as bucket-path for AWS. Camunda doesn't insert the tenant ID into the path for you

Compare document store locations across tenants

Camunda resolves a location for every configured document store at startup, then compares the locations of all tenants. A location is the provider, a namespace, and a key prefix:

  • The namespace is the container no key can escape — a bucket, a blob container, or a directory.
  • The key prefix is the string every key inside that namespace starts with.

Two tenants overlap when the provider and namespace match and one key prefix is a prefix of the other. Overlap is broader than equality because a document ID is caller-supplied and appended to the key prefix as given. With the prefixes tenant and tenant-b- in one bucket, a request against the first store for the document ID -b-invoice resolves to the second store's tenant-b-invoice.

A separator changes nothing: docs/ reaches docs/archive/ through the document ID archive/invoice, because no object storage service treats / in a key as a path boundary. Any prefix nested inside another tenant's prefix is therefore rejected, including a bucket or container root paired with a path inside it.

Give every tenant that shares a bucket or container its own sibling prefix. No layout lets one tenant own the root while another owns a path within it, and isolation is enforced by this check at startup rather than by inspecting document IDs at runtime.

When the check fails, the cluster doesn't start, and the error names each conflict:

Physical tenants must not share a document store location, or they would read and write
into the same backing storage. Use a distinct bucket, container, or path per tenant, and
never nest one tenant's path inside another's — a nested path is reachable through a
caller-supplied document id, which no object store bounds at '/'. Conflicts: tenant
default's document store location [provider=aws, namespace=[camunda-documents, ],
keyPrefix=''] encloses tenant tenanta's [provider=aws, namespace=[camunda-documents, ],
keyPrefix='tenant-a/']

Limitations of location comparison

LimitationEffect
Aliases aren't resolvedTwo endpoint URLs or DNS names fronting the same backend are treated as separate locations, so a genuine overlap isn't detected.
Local paths are compared case-insensitively on every platformOn a case-sensitive filesystem, two directories differing only in case are reported as a collision even though they're isolated.
Nested local directories aren't comparedpath: /var/docs and path: /var/docs/tenant-b are two namespaces with empty prefixes. The local store rejects /, \, and .. in a document ID, so the parent can't descend into the child.

Operational considerations

Backup and restore

  • Per-tenant: Back up one tenant's RDBMS schema or document bucket individually. A per-tenant Elasticsearch/OpenSearch backup is not yet available (see the note below).
  • Full cluster: Back up all schemas, all index prefixes, all buckets simultaneously
  • Restore options: Individual tenant or full cluster from backup

Example — back up Tenant A only:

# RDBMS
pg_dump -h db.example.com -U user tenant_a_schema > backup.sql

# Document store (S3)
aws s3 sync s3://camunda-documents/tenant-a/ ./backup/
note

Elasticsearch and OpenSearch backups are created through the web applications backup endpoint, which operates at the cluster level. A per-tenant Elasticsearch/OpenSearch backup endpoint is not yet available and is planned as part of the management API.

Cross-tenant isolation

Storage isolation prevents data leakage structurally:

  • RDBMS: Each schema is logically separate
  • ES/OS: Index prefixes are distinct boundaries
  • Document Store: Separate buckets or subpaths

Risks to avoid:

  • Don't share JDBC connection URLs between tenants (for Oracle, two tenants can share the same URL while remaining isolated by distinct authenticated database users)
  • Don't overlap index prefixes
  • Don't point two tenants to the same bucket or container without distinct sibling subpaths. Don't nest one tenant's subpath inside another's, and don't leave one tenant on the bucket or container root

Scaling and capacity planning

  • RDBMS: Monitor schema size per tenant; high-traffic tenants may need dedicated instances
  • ES/OS: Monitor index size per prefix; set retention policies independently per tenant. If ES/OS is shared across multiple Physical Tenants, also monitor overall cluster health and capacity.
  • Document Store: Monitor bucket size per tenant; set lifecycle policies for archival

Migration scenarios

ScenarioSteps
Add tenantCreate storage backend → Validate connectivity → Add config → Rolling restart
ConsolidateBackup source → Create new backend → Update config → Restore → Verify
Split tenantPlan data distribution → Backup → Create stores → Restore to each → Restart

Known limitations in 8.10

note

Cannot mix secondary storage backends across tenants. All Physical Tenants in a cluster must use the same secondary storage type — either all RDBMS or all Elasticsearch/OpenSearch. A cluster where tenant A uses RDBMS and tenant B uses Elasticsearch is not supported in 8.10. This constraint exists in the Query API stack, not the exporter layer.

Custom exporter configuration merge (alpha3)

In 8.10 alpha3, per-tenant and root-level custom exporter configurations are not merged. If you have a custom exporter (for example, a Kafka exporter) and want each tenant to publish to a different topic, you must declare the full exporter configuration separately under each Physical Tenant's section — you cannot declare it once at root level and override only the topic per tenant. This will be addressed in a later alpha. See camunda/camunda#55155.

Storage configuration matrix

AspectRDBMSElasticsearch/OpenSearchDocument Store
IsolationSeparate schema/databaseSeparate cluster OR index prefixSeparate bucket OR sibling subpath
Per-tenant configJDBC URLurl + index-prefixBucket + prefix
Collision detectionStartup errorStartup errorStartup error
Unavailable behaviorStartup failureStartup failureRuntime error (no fallback)
Mixed vendorsYesYes (ES or OpenSearch)Yes (different cloud providers)