Secrets management
Store, inject, and rotate Bondery secrets in staging and production.
Secrets management
Bondery secrets are ordinary environment variables with the BONDERY_PRIVATE_* prefix (plus DATABASE_URL for Postgres). Local development uses root .env.local; production should use a secrets manager or your platform's encrypted env store — not committed files or chat messages.
For which variables exist and what they do, see Environment configuration and Authentication (contributors).
Principles
- Generate once per environment — staging and production each get their own values. Never copy production secrets into local
.env.local. - Inject at runtime — containers and processes read secrets from the environment. Do not bake secrets into Docker images.
- Rotate on a schedule or on incident — document the blast radius before rotating. Some secrets invalidate all active sessions.
- Audit access — restrict who can read deploy env in Dokploy, AWS, or your vault. Prefer break-glass access over shared passwords.
Where to store secrets
| Environment | Recommended approach |
|---|---|
| Local dev | Root .env.local (gitignored). Generate with openssl rand -hex 32. |
| Self-host (Dokploy) | Dokploy project Environment tab — mark sensitive keys as secret. Same BONDERY_* names as deploy/bondery/.env.example. |
| CI | GitHub Actions encrypted secrets for deploy keys only. CI test Postgres uses ephemeral credentials in the workflow file, not production values. |
| Cloud (optional) | AWS Secrets Manager, GCP Secret Manager, or HashiCorp Vault — sync into container env at deploy time via your orchestrator or an init sidecar. |
Bondery does not ship a custom secrets agent. Any store that can set process environment variables before api / webapp start is sufficient.
Dokploy (current self-host path)
- Open the Bondery stack project → Environment.
- Paste
BONDERY_PRIVATE_*values from a password manager or one-time generation. - Redeploy affected services (no image rebuild required for env-only changes).
Keep a password manager record per environment listing secret names, generation date, and last rotation — Dokploy stores values but is not a full audit/rotation system.
External secrets manager (larger installs)
Typical pattern:
Secrets Manager (AWS SM / Vault)
→ deploy hook or sidecar fetches JSON
→ exports BONDERY_PRIVATE_* before docker compose up
→ api / webapp containers inherit envStore one JSON object per environment, keys matching the manifest canonical names. The api service pre_start init containers need the same OAuth and database secrets as the main API process because release-migrate provisions OAuth clients after prisma migrate deploy.
Secret inventory
| Secret | Used by | Rotation impact |
|---|---|---|
BONDERY_PRIVATE_BETTER_AUTH_SECRETS | API | Medium — versioned rotation can avoid mass logout when adding a new version first; removing the last old version invalidates sessions still encrypted with it |
BONDERY_PRIVATE_WEBAPP_SESSION_SECRET | Webapp | High — invalidates all webapp session cookies |
BONDERY_PRIVATE_WEBAPP_OAUTH_CLIENT_SECRET | API (hash), webapp (plaintext) | Medium — update env on API + webapp, re-run provision-oauth-clients, users may need to re-login |
BONDERY_PRIVATE_API_KEY_PEPPER | API | High — all existing API keys stop validating; create new keys first |
BONDERY_PRIVATE_POSTGRES_PASSWORD | DB, API | High — requires coordinated DB user password change + env update + restart |
BONDERY_PRIVATE_AUTH_GITHUB_CLIENT_SECRET | API | Low — update GitHub app + env; no user session impact |
BONDERY_PRIVATE_AUTH_LINKEDIN_CLIENT_SECRET | API | Low — same as GitHub |
BONDERY_PRIVATE_REDIS_URL | API | Medium — may embed password; rotating Redis auth drops in-flight rate-limit state |
BONDERY_PUBLIC_* OAuth client ids are not secrets. Treat them like usernames — stable per environment, safe in config repos.
Rotation runbooks
BONDERY_PRIVATE_BETTER_AUTH_SECRETS
Format: version:secret[,version:secret...] — highest version (current signing key) first, older decrypt-only versions after.
Bootstrap:
openssl rand -hex 32 # use as: BONDERY_PRIVATE_BETTER_AUTH_SECRETS=1:<value>When: annual schedule, suspected compromise, or employee offboarding with deploy access.
Zero-downtime rotation:
- Generate a new secret:
openssl rand -hex 32. - Prepend it as a new version:
BONDERY_PRIVATE_BETTER_AUTH_SECRETS=2:<new>,1:<old>. - Deploy
api. - Wait for natural session refresh / re-login (or a maintenance window).
- Remove the old version:
BONDERY_PRIVATE_BETTER_AUTH_SECRETS=2:<new>.
Hard cutover (logs everyone out): replace the entire value with a single new 1:<secret> and redeploy.
- Verify:
GET https://<api-domain>/statusand a test login on webapp + mobile. - Record rotation date in your password manager.
BONDERY_PRIVATE_WEBAPP_SESSION_SECRET
When: annual schedule or webapp-only compromise suspicion.
- Generate new value (
openssl rand -hex 32). - Update webapp env only.
- Redeploy webapp.
- All users must log in again; API sessions and mobile tokens are unaffected.
BONDERY_PRIVATE_WEBAPP_OAUTH_CLIENT_SECRET
When: secret may have leaked from webapp env backup or logs.
- Generate new secret (
openssl rand -hex 32). - Update on both API and webapp env (
BONDERY_PRIVATE_WEBAPP_OAUTH_CLIENT_SECRET). - Redeploy
api(or runpnpm run provision-oauth-clientsagainst production) so the API's stored hash updates. - Redeploy
apiandwebapp. - Active webapp sessions may fail token refresh until users re-login.
BONDERY_PRIVATE_API_KEY_PEPPER
When: pepper may have leaked — rare; prefer creating new API keys over rotating pepper.
- Before changing pepper: ask integrators to create new API keys in Settings.
- Update pepper in API env.
- Redeploy API.
- Old keys return
401 invalid_api_key. Delete old keys from the admin UI after integrators confirm migration.
BONDERY_PRIVATE_POSTGRES_PASSWORD
When: database credential rotation policy.
ALTER USER postgres PASSWORD '...'inside Postgres (or recreate via compose on fresh installs only).- Update
BONDERY_PRIVATE_POSTGRES_PASSWORDin deploy env (compose rebuildsDATABASE_URLfor services). - Restart
db(if needed), thenapi, and any job workers. - Verify migrations and a sample authenticated API call.
OAuth client ids (BONDERY_PUBLIC_WEBAPP_OAUTH_CLIENT_ID, BONDERY_PUBLIC_OAUTH_CLIENT_ID)
When: rarely — only if you intentionally want new client registrations (e.g. extension ID change in production).
- Generate new ids if needed (secrets manager or
openssl rand -hex 16). - Update env across affected apps.
- Run provisioning (
apiredeploy /pre_start, orpnpm run provision-oauth-clients -w apimanually). - Old refresh tokens for the previous client id will not work.
First-time production setup
Generate all secrets before the first deploy:
openssl rand -hex 32 # BONDERY_PRIVATE_BETTER_AUTH_SECRETS=1:<value>
openssl rand -hex 32 # BONDERY_PRIVATE_API_KEY_PEPPER
openssl rand -hex 32 # BONDERY_PRIVATE_WEBAPP_SESSION_SECRET
openssl rand -hex 32 # BONDERY_PRIVATE_WEBAPP_OAUTH_CLIENT_SECRET
openssl rand -hex 16 # BONDERY_PUBLIC_WEBAPP_OAUTH_CLIENT_ID
openssl rand -hex 16 # BONDERY_PUBLIC_OAUTH_CLIENT_ID
openssl rand -base64 24 | tr -d '/+=' | head -c 32 # BONDERY_PRIVATE_POSTGRES_PASSWORDStore them in your secrets manager, map into deploy/bondery/.env, then deploy. The api service pre_start runs release-migrate, which applies schema migrations and provisions OAuth clients automatically.