Manifest backup and restore
The config manifest is the entire control-plane configuration serialized as one YAML document: every tenant, database, pool, federated source, role, group, user, and grant. Export it to back up or version-control your configuration, then import it to restore, clone an environment, or apply a reviewed change. The manifest holds configuration only; it does not contain table data or the DuckLake catalogs.
Manifest export and import are cross-tenant operations and are restricted to superusers. A tenant-scoped session is rejected with 403 superuser_required; a static QOD_API_KEY caller is admitted. The examples assume an admin token in $TOKEN and the manager base URL in $MGR.
Export
curl -sS -H "X-API-Key: $TOKEN" "$MGR/api/manifest/export" > manifest.yaml
GET /api/manifest/export returns the manifest as application/yaml. The top of the document records provenance:
apiVersion: quack-on-demand/v1
kind: ConfigManifest
exportedAt: 2026-06-10T12:00:00Z
exportedFrom:
managerVersion: 0.3.2
hostname: qod-1
What the export contains
The document mirrors the object hierarchy:
tenants[]- each with itsauthProvider/authConfig, and nested:tenantDbs[]-kind,metastore,dataPath,objectStore,defaultDatabase,defaultSchema, and nestedfederatedSources[](with theirsecrets[]).pools[]-tenantDb,roleDistribution,maxConcurrentPerNode,disabled, and optionalcohorts[]placement.identities[]- external identity mappings for the tenant.
roles[]- each(tenant, name)with itspermissions[](catalog/schema/table/verb).groups[]- each(tenant, name)with its assignedroles[].users[]-tenant(omitted/null for a superuser),username,role,enabled, and the user'sroles[],groups[], andpoolGrants[].
Redaction
Secrets are never written in clear text on export:
- User
passwordis omitted entirely from exported users. - Federated secret
values are written as***REDACTED***; anexternalRefis written verbatim.
This is what makes a plain export-then-import round-trip safe: omitted passwords and redacted secret values are preserved from the existing rows on import (see below), so a round-trip never rotates or wipes a credential.
Import
curl -sS -H "X-API-Key: $TOKEN" -X POST "$MGR/api/manifest/import" \
-H 'Content-Type: text/plain' --data-binary @manifest.yaml
POST /api/manifest/import takes the YAML body and returns a summary of how many top-level resources were in the manifest:
{"tenants":2,"tenantDbs":3,"pools":4,"roles":5,"groups":2,"users":7}
Import validates the whole document before writing anything. On failure it returns 400 and changes nothing:
invalid-yaml- the body did not parse as YAML.invalid-manifest- validation failed: a wrongapiVersion(must bequack-on-demand/v1), duplicate keys (tenant name,(tenant, role),(tenant, group),(tenant, user), or a nested duplicate), or a user/role/group that references a tenant not present in the manifest or already in the database.
After a successful import the manager reloads its in-memory caches (tenants, databases, pools, RBAC effective sets) immediately. No restart is needed for the new configuration to take effect.
Apply semantics
This is the part to understand before importing a hand-edited file. The rules differ between top-level and nested resources:
- Top level is additive (upsert only). Tenants, roles, groups, and users that appear in the manifest are created or updated. Top-level resources that are absent from the manifest are left untouched: importing a manifest that lists only
tenant: acmedoes not deletetenant: widgets. None of the top-level lists are required, so a manifest may omitusers,roles, orgroupsentirely. An omitted user is ignored, never deleted; the importer only ever upserts the users it finds in the file. - Nested collections under a present parent are replace (delete-then-upsert). For any parent that is in the manifest, its child collections are made to match the manifest exactly. Children present in the manifest are upserted; children that exist in the database but are missing from the manifest are deleted.
Because nested collections are replaced, omitting a child under a parent you do include removes it. If you export, then hand-edit a tenant to drop one pool from its pools list and re-import, that pool is deleted. A role's permissions are fully replaced; a user's poolGrants are fully replaced. To change one tenant safely, keep all of its databases, pools, roles, and grants in the file, or edit a full export rather than a partial document.
Passwords and secrets on import
- A user with no
passwordfield reuses the bcrypt hash captured from the existing row at the start of the import. A user with apasswordvalue sets it (the value is treated as a bcrypt hash if it looks like one, otherwise as plaintext to be hashed). - A federated secret left as
value: "***REDACTED***"(and noexternalRef) reuses the existing stored value. This is why a redacted export re-imports without re-typing credentials.
Typical workflows
- Backup. Export on a schedule and store
manifest.yamlin version control. The redaction means the file carries no plaintext credentials. - Promote a change. Export, edit in a reviewed pull request, re-import. Validation plus all-or-nothing apply makes a bad edit fail before it touches the store.
- Clone an environment. Import a source environment's manifest into a fresh manager. Provide real
password/ secretvalues for anything the target does not already have, since there is nothing to reuse on a first import.
For the objects the manifest carries, see "Tenants and databases", "Pools and cohorts", Federation, and the Access control model.