Skip to content
ConfigurationStorage

Storage

Persist drydock state by mounting /store as a volume.

The SQLite storage and import behavior below first appears in v1.8.0-rc.1, an opt-in test candidate. Stop Drydock and back up the complete store directory before upgrading. v1.7 and earlier releases persist their state in dd.json; do not apply the v1.8 downgrade procedure to an installation that has not been upgraded.

If you want the state to persist after the container removal, you need to mount /store as a volume.

Drydock's structured store records — containers, settings, secrets, agent and API keys, audit history, notification history, image-restore backup records, notification rules, the approval queue, update operations, and login sessions — live in one SQLite database, DD_STORE_DB_FILE (dd.sqlite by default). SBOM document bodies are separate files under /store/sbom; the database keeps their references. Mounting /store covers both the database and those files.

A plain file copy of dd.sqlite (and its -wal/-shm sidecars, if present) taken while drydock is running can catch it mid-write and produce a corrupt backup. Either stop the container first and then copy the files, or take a consistent live backup with sqlite3 dd.sqlite ".backup /path/out.sqlite" (or VACUUM INTO) instead. Restore from one complete, consistent snapshot, never from a copy taken mid-write.

For a full backup that includes SBOM documents, stop Drydock and copy the store directory. A SQLite-only live backup contains their database references, not the separate document files under /store/sbom.

Upgrade from JSON

When the SQLite database does not exist, startup imports an existing DD_STORE_FILE through a temporary database before putting the completed database in place. Import failure stops startup. An existing SQLite database is opened without re-importing JSON, and a fresh install with no legacy file creates no JSON store.

Published v1.6.1 and v1.7.0-rc.24 builds already store sessions in a separate JSON file (dd-sessions.json by default). The v1.8 importer derives the sibling session filename from DD_STORE_FILE, replacing its last extension with -sessions.json (for example, tenant.json → tenant-sessions.json; a name without an extension gets the suffix appended). It prefers that file when present, otherwise reading the legacy Sessions collection in the main JSON store. A custom store never imports an unrelated dd-sessions.json. It leaves the session JSON file untouched.

After import, the main JSON file is renamed to <DD_STORE_FILE>.pre-1.8.bak unless that backup already exists. An existing backup is never overwritten: Drydock logs a warning and leaves the imported JSON file in place. These are pre-upgrade snapshots, not live copies of the SQLite store.

Variables

Env varRequiredDescriptionSupported valuesDefault value when missing
DD_STORE_PATH⚪Directory where the database file is storeddirectory path/store
DD_STORE_FILE⚪Legacy database filename, imported only when it exists and DD_STORE_DB_FILE does not. A fresh v1.8 install never creates it. After import it is renamed to <DD_STORE_FILE>.pre-1.8.bak unless that backup already exists, in which case both legacy files are preserved and a warning is logged. An existing SQLite database is opened without re-importing the JSON file.filenamedd.json
DD_STORE_DB_FILE⚪SQLite database filename, stored alongside DD_STORE_FILE in the same directory. Database records live here: app metadata, settings, the session secret, UI preferences, Portwing agent keys and name bindings, API keys with their scopes, audit entries, notification history and the notification outbox, image-restore backups, notification rules and the approval queue, the update-lifecycle and update-policy-retention caches, containers, update operations, and login sessions.filenamedd.sqlite
DD_DEFAULT_CACHE_MAX_ENTRIES⚪Shared default entry cap used by several in-memory caches when their specific cap is not setinteger (>0)500
DD_CONTAINERS_QUERY_CACHE_MAX_ENTRIES⚪Max cached container-list query results retained in memoryinteger (>0)DD_DEFAULT_CACHE_MAX_ENTRIES (500)
DD_SECURITY_STATE_CACHE_TTL_MS⚪Time-to-live for cached per-container security state used to survive short container recreation gapsinteger (>0)900000 (15 min)
DD_SECURITY_STATE_CACHE_MAX_ENTRIES⚪Max cached per-container security-state entries retained in memoryinteger (>0)DD_DEFAULT_CACHE_MAX_ENTRIES (500)
DD_UPDATE_LIFECYCLE_CACHE_TTL_MS⚪Time-to-live for cached update lifecycle timestamps used while a pending update candidate remains the sameinteger (>0)1800000 (30 min)
DD_UPDATE_LIFECYCLE_CACHE_MAX_ENTRIES⚪Max cached update lifecycle entries retained in memoryinteger (>0)DD_DEFAULT_CACHE_MAX_ENTRIES (500)
DD_UPDATE_POLICY_RETENTION_CACHE_TTL_MS⚪Time-to-live for cached update-policy retention decisionsinteger (>0)604800000 (7 days)
DD_UPDATE_POLICY_RETENTION_CACHE_MAX_ENTRIES⚪Max cached update-policy retention entries retained in memoryinteger (>0)DD_DEFAULT_CACHE_MAX_ENTRIES (500)
DD_UPDATE_OPERATION_MAX_ENTRIES⚪Max update/rollback operation records to retain (oldest pruned when exceeded)integer (>0)DD_DEFAULT_CACHE_MAX_ENTRIES (500)
DD_UPDATE_OPERATION_RETENTION_DAYS⚪Days to retain update operation records before pruninginteger (>0)30
DD_UPDATE_OPERATION_ACTIVE_TTL_MS⚪Maximum age (ms) an operation may remain in an active status (queued or in-progress) before it is terminalised to the non-notifying expired status. Applied at startup and during housekeeping to clear stuck operations left behind by an unclean shutdown; unlike failed, an expired operation emits no "update failed" notification.integer (>0)1800000 (30 min)

Examples

services:
  drydock:
    image: codeswhat/drydock
    ...
    volumes:
      - /path-on-my-host:/store

Downgrade Notes

Rolling back from v1.8 to v1.7 or earlier needs dd.json back, not dd.sqlite: stop drydock, rename <DD_STORE_PATH>/<DD_STORE_FILE>.pre-1.8.bak back to <DD_STORE_FILE> (dd.json.pre-1.8.bak → dd.json with the defaults), then start the older build. It only has what existed at the moment of the v1.8 upgrade's one-time import — anything written afterward (new containers, settings changes, sessions) lived in dd.sqlite alone and does not roll back with it.

Keep any pre-upgrade session JSON file with the main JSON snapshot (dd-sessions.json with the default filename on v1.6.1 and v1.7.0-rc.24). The importer leaves it in place, but it does not receive session changes made by v1.8.

If the importer warned that a backup already existed, it left the imported JSON file in place. Preserve both files and identify the intended snapshot before restoring; the existing .pre-1.8.bak may belong to an earlier upgrade. A fresh v1.8 installation has no pre-upgrade JSON snapshot to restore.

The dd.json store written by v1.5.1 and later tolerates unknown fields, so a later v1.5.x store can usually be read after rolling back to another v1.5.1+ build.

Rolling back from v1.5.1 to v1.5.0 is the exception: v1.5.0 rejects fields added after it shipped. If you must roll back that far, stop Drydock first, back up /store/dd.json, then remove details.startedAt and image.softwareVersion from stored container records before starting v1.5.0.