Each workspace is backed by its own directory on the service’s filesystem — that
is how the underlying engine persists a password-locked container. What the API
deliberately does not expose is where that directory goes: there is no
filesystem field in tenants.json and no path parameter on
POST /v1/workspaces. The location is derived, not chosen.
One data root, set by the operator
Every workspace directory lives under a single root the operator sets once, in
the environment — WATIVE_DATA_ROOT. Nothing else in the service builds a path
from configuration. In the shipped image it defaults to
/var/lib/tee-docker/data, which the Compose file bind-mounts from a fixed host
path (/opt/tee-docker/data).
One folder per workspace, derived
Within that root, a workspace’s folder is computed from two trusted values — the
authenticated tenant (from X-Api-Key, never a body field) and the
validated slug:
$WATIVE_DATA_ROOT/
└── <tenantId>/
└── <workspaceSlug>/
├── config workspace metadata
├── network the network registry
├── asset asset catalogue
├── log append-only operation log
└── accounts/ one entry per account (the encrypted key material)No path component ever comes from raw client input: the slug grammar excludes
. and /, so traversal is impossible by construction, and a containment check
rejects any resolved path that escapes the tenant’s own subtree. That is why
POST /v1/workspaces takes only { slug, password } — placement is a uniform
operator concern, identical for every tenant, not a per-request or per-tenant
decision.
Creating the workspace is the only operation allowed to initialize storage; every later unlock opens an existing container and refuses to create an empty one in its place.
Two records behind every workspace
A workspace exists as two things that must stay in step:
| Record | Where | What it is |
|---|---|---|
| The container | $WATIVE_DATA_ROOT/<tenant>/<slug>/ |
The engine’s encrypted workspace — accounts, wallets, key material |
| The ledger row | state.json, under TEE_STATE_DIR |
The service’s index of which workspaces a tenant has, and its wallet counts |
Create writes both; delete removes both. This split is why backups must capture
state/ and data/ together, with the service stopped — a backup that
catches them at different moments, or restores one without the other, can strand
a workspace whose ledger row and on-disk container disagree. See the
deploy backup notes.
The three directories a deployment keeps
Storage is one of three separate directories, each with its own environment variable, so their concerns never mix:
| Directory | Env var | Container default | Holds |
|---|---|---|---|
| Config | TEE_CONFIG_DIR |
/app/config |
tenants.json (bind-mounted read-only) and errors.json (baked into the image) |
| State | TEE_STATE_DIR |
/var/lib/tee-docker/state |
state.json — the workspace/quota ledger |
| Data | WATIVE_DATA_ROOT |
/var/lib/tee-docker/data |
the per-workspace container folders above |
TEE_SECRET_HMAC_KEY — which authenticates tenants and signs session tokens —
lives only in the environment, never on disk. It does not encrypt the containers
themselves: each workspace’s data/ is locked under its own password, which the
service never stores. A restored data/ still cannot be reached without the key,
because no tenant can authenticate to unlock it.
Related
- Deploy — the persistent directories, mounts, and backups in full
- Workspaces and tokens — what a workspace is and how a token opens one
- Networks — the network registry stored under
network