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 var | Required | Description | Supported values | Default value when missing |
|---|---|---|---|---|
DD_STORE_PATH | ⚪ | Directory where the database file is stored | directory 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. | filename | dd.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. | filename | dd.sqlite |
DD_DEFAULT_CACHE_MAX_ENTRIES | ⚪ | Shared default entry cap used by several in-memory caches when their specific cap is not set | integer (>0) | 500 |
DD_CONTAINERS_QUERY_CACHE_MAX_ENTRIES | ⚪ | Max cached container-list query results retained in memory | integer (>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 gaps | integer (>0) | 900000 (15 min) |
DD_SECURITY_STATE_CACHE_MAX_ENTRIES | ⚪ | Max cached per-container security-state entries retained in memory | integer (>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 same | integer (>0) | 1800000 (30 min) |
DD_UPDATE_LIFECYCLE_CACHE_MAX_ENTRIES | ⚪ | Max cached update lifecycle entries retained in memory | integer (>0) | DD_DEFAULT_CACHE_MAX_ENTRIES (500) |
DD_UPDATE_POLICY_RETENTION_CACHE_TTL_MS | ⚪ | Time-to-live for cached update-policy retention decisions | integer (>0) | 604800000 (7 days) |
DD_UPDATE_POLICY_RETENTION_CACHE_MAX_ENTRIES | ⚪ | Max cached update-policy retention entries retained in memory | integer (>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 pruning | integer (>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:/storeDowngrade 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.