Secrets

The DataMind Installer juggles three kinds of secret, and they are stored in three different places:

Generated secrets and where they live

On first boot the installer creates a named Docker volume (called delamain_secrets for the production stack, delamain_secrets_dev for the dev stack) and writes its generated secrets into it as files. The backend and the migration init job mount that volume at /usr/src/app/secrets; the Postgres container mounts the same volume at /run/secrets.

FileGenerated byValueUsed as
jwt.secretInstaller backend, on first boot64 random bytes, hex-encodedJWT_SECRET — signs access and refresh tokens
encr.keyInstaller backend, on first boot32 random bytes, base64NEST_ENCR_KEY — encrypts stored secret values at rest
curato.tokenInstaller backend, on first boot48 random alphanumeric charactersCURATO_SERVICE_TOKEN — the shared secret the curato service presents to drive deployments
pg.passPostgres container entrypoint, on first boot24 random bytes, hex-encodedPOSTGRES_PASS — the database password

Each file is written with owner-only permissions (0600). On every subsequent start the installer loads the existing files instead of regenerating them, so secrets are stable across restarts and updates. If pg.pass is missing, the backend refuses to start — it will not invent a database password the Postgres container does not agree with.

Important

These four values are not set in .env. The one exception is that the value of CURATO_SERVICE_TOKEN is additionally written into the deployed stack's environment file, which is a different file from the installer's own .env. It is written directly from the generated token rather than from a database row, so no operator edit can drift it away from the value the installer compares against.

To back up the generated secrets, back up the volume. Losing the volume means losing the JWT signing secret, the encryption key and the database password.

Operator-supplied secrets

The operator supplies two values, both stored in the installer's database and encrypted with the NEST_ENCR_KEY key material:

SecretHow it is enteredHow it is exposed
AZURE_CLIENT_SECRETAdvanced configuration → System secrets, or a direct API call to /api/system-secrets/azureNever returned by any endpoint. The API exposes only whether it is configured and whether the stored value still validates against Azure AD. The UI shows a Configured / Not set badge and an empty password field — the stored value is never echoed back.
VM_USERMigration screen (/api/migrate/vm-user)Returned to administrators as the stored VM user; used to replace <USER> in the deployed stack's environment file.

Encryption uses AES-256-GCM with a random per-value initialisation vector, serialised with a v2: prefix. A row is encrypted when it is marked secret or when its source is generated.

How the UI and API expose secrets

The configuration endpoint returns the deployed stack's rows with a secret flag. Its behaviour depends on the caller's role:

The Advanced configuration screen is read-only for non-administrators and requires an administrator account to edit. Rows flagged as secret are shown with a key icon and a password-style field with a reveal toggle. The AZURE_CLIENT_SECRET field is handled separately and never displays the stored value, only its configured status and validation result.

Warning

The masking above protects the deployed stack's configuration rows. It does not mean secrets are absent from the API: an administrator sees decrypted values. Treat administrator access to the installer as access to every secret it manages.

How secrets reach containers

Two independent paths carry secrets to running containers:

Because the deployed stack's secret rows are stored in the installer's database and rendered into that file on every generation, rotating them means editing the value in the UI and then recreating the affected services. See Rotating values.