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 and remains available in v1.8.0-rc.6 opt-in test builds. 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 builds (including its rc.24 candidate) 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.
Migration snapshots and downgrade protection
Before applying a pending schema migration to an existing database, Drydock writes a consistent snapshot next to it, dd.sqlite.pre-migration-v<schema version>-<timestamp> (readable by the owner only), and logs its path. The newest three are kept; older ones are deleted. A fresh database gets no snapshot, and neither does a start with nothing to migrate. If the snapshot can't be written, for example because the volume is full, startup stops with an error instead of migrating without a restore point. Free space or fix permissions on the store directory and restart.
A database that a newer Drydock build already migrated is not opened by an older one. The older build logs one error naming both schema versions and exits with status 1, so a downgrade (or a self-update rollback to the previous image) can't corrupt it. To go back, either run a build at least as new as the one that last ran, or stop Drydock, copy the dd.sqlite.pre-migration-* snapshot taken before that migration over dd.sqlite, delete any dd.sqlite-wal and dd.sqlite-shm files beside it, and start the older build. Anything written after the snapshot is not in it.
Builds up to v1.8.0-rc.6 have neither the snapshot nor the check, so a guarded build protects against downgrades only to builds that also include it.
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:1.8.0-rc.6
...
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, including its rc.24 candidate). 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.