Credentials & secrets¶
Where every secret lives, how it reaches the containers, how to rotate it, and what must never be in git or in a log line. Copilot-specific detail is in Copilot credentials; vendor app registration in ACC / Procore activation.
Where secrets live¶
| Secret | Set where | Consumed by | Notes |
|---|---|---|---|
DATABASE_URL, POSTGRES_PASSWORD |
Dokploy → Compose app → Environment |
migrate, api, worker, backup |
POSTGRES_PASSWORD is required by the compose file (${POSTGRES_PASSWORD:?…}) |
JWT_SECRET (+ JWT_SECRET_OLD) |
Dokploy Environment | api |
signs access/refresh tokens; ≥ 32 chars |
APP_SECRET (+ APP_SECRET_OLD) |
Dokploy Environment | api, worker |
AES-256-GCM key ring for MFA secrets, integration credentials, webhook signing secrets, SAML certs; HMAC for signed storage/point-cloud URLs and copilot confirmation tokens; ≥ 32 chars |
S3_ACCESS_KEY_ID, S3_SECRET_ACCESS_KEY (S3_ENDPOINT, S3_BUCKET, S3_FORCE_PATH_STYLE, STORAGE_DRIVER) |
Dokploy Environment | api, worker, migrate (bucket creation) |
MinIO in the compose, or R2/S3 — see docs/storage-r2-s3.md |
SMTP_HOST/PORT/USER/PASS/FROM |
Dokploy Environment | api, worker |
unset = notifications stay in-app; invitations still work via Copy link |
REDIS_URL, REDIS_KEY_PREFIX |
Dokploy Environment | api, worker |
both must agree on the prefix or jobs never meet |
APS_CLIENT_ID/SECRET, AUTODESK_CLIENT_ID/SECRET, PROCORE_CLIENT_ID/SECRET/BASE_URL |
Dokploy Environment | api, worker |
vendor app credentials; the user tokens obtained through OAuth are stored encrypted in the database, never in env |
GOOGLE_API_KEY, OPENROUTER_API_KEY, DEEPSEEK_API_KEY, COPILOT_* |
Dokploy Environment | api |
see the copilot runbook |
STRIPE_SECRET_KEY, STRIPE_WEBHOOK_SECRET |
Dokploy Environment | api |
unset = billing off, every workspace on the free plan |
PLATFORM_ADMIN_EMAILS |
Dokploy Environment | api |
allowlist for /api/admin/* (backup status, relation backfill, compliance evidence); fail-closed when unset |
| Planon base URL + API key, accounting credentials, SAML certificates, OAuth app client secrets, SCIM tokens, workspace API keys, webhook signing secrets | entered in the UI / API by workspace or project admins | api |
stored in Postgres: API keys hashed; the rest encrypted under APP_SECRET |
Generate secrets with node -e "console.log(require('crypto').randomBytes(32).toString('hex'))".
The full variable list with comments is apps/server/.env.example (the rotation and platform-admin
names are listed there under "Operator settings").
The compose allowlist trap¶
Containers only see the variables the compose file's environment: block forwards. Setting a
variable in Dokploy that the block does not list produces no error and no effect — the feature
just stays off. Every copilot key hid behind this once — and so did every operator setting on this
page: until the documentation pass of 2026-09-17 the anchor forwarded neither JWT_SECRET_OLD /
APP_SECRET_OLD (a rotation had no old-key window), nor PLATFORM_ADMIN_EMAILS (the /api/admin/*
routes were unreachable in Production), nor INTEGRATIONS_ENABLED, the Stripe pair, or the
DWG_CONVERTER_PATH / FFMPEG_PATH / PDAL_PATH / POTREE_CONVERTER_PATH binaries. All are
forwarded now, each tolerating the empty string compose renders for an unset variable.
apps/server/tests/structural/compose-env-allowlist.test.ts pins every name on this page
(MUST_FORWARD) and proves each parses "" safely; when you introduce a new server variable, add
it to the environment: anchor in docker-compose.yml and to that test.
Rotation¶
JWT_SECRET/APP_SECRET— move the current value toJWT_SECRET_OLD/APP_SECRET_OLD, set a new current value, save (Dokploy redeploys). Signing and encryption use the new key immediately; verification and decryption try the new key then the old one, so live sessions, signed URLs, MFA secrets and stored integration credentials keep working. Once the longest-lived artefact has expired (refresh tokens:REFRESH_TOKEN_TTL_DAYS, default 30) remove the_OLDvariable. RotatingAPP_SECRETwithout the_OLDwindow makes every encrypted credential unreadable — users would have to reconnect every vendor and re-enrol MFA.- Vendor client secrets — replace in Dokploy; existing user tokens keep working until they expire, then users Re-authorize.
- Copilot keys — replace in Dokploy; the next request uses the new key. Removing all of them turns the feature off with an honest 503.
- Database / storage / SMTP passwords — rotate at the provider, update Dokploy, redeploy; the
backupservice readsPOSTGRES_PASSWORDtoo. - UI-managed secrets (API keys, SCIM tokens, OAuth app secrets, webhook secrets) — they are shown once at creation and cannot be re-shown; rotation = create new, switch the consumer, revoke old.
Every rotation that touches auth is worth a line in the audit log's context (the log records
sign-ins, not env changes); note it in docs/PHASE-EXECUTION-LOG-2026-09.md.
What must never be in source control¶
apps/server/.env, any.env.*with values, Dokploy exports,docker-compose.override.ymlwith values..env.examplecarries names and comments only.- Vendor keys, client secrets, JWT/APP secrets, database URLs with passwords, SMTP passwords.
- Screenshots or logs containing bearer tokens,
bz_…API keys,whsec_…webhook secrets, SCIM tokens, or the copilot confirmation token. - Customer data: exported IFCs, documents, photos, or database dumps.
What must never be in logs¶
- The API redacts integration
configbefore returning it (modules/integrations/redact.tsdrops any key matchingsecret|token|password|passphrase|credential|private|signing|bearer|salt| hmac|key|cert|pem|jwt|otp|seed). Keep that pattern when adding config fields. - Copilot: prompts and tool results go to the model vendor by design; the confirmation token and the vendor keys are never logged. A vendor failure logs the vendor's message only ("copilot provider failed; trying the next one").
- Never
console.loga request body on an auth, integration or billing route.
Checking a deployment's secret posture¶
curl -s https://bim.addmedad.top/api/health→ the api is up; it never returns configuration.- Workspace settings › Access review lists members without MFA and every API key.
GET /api/admin/compliance/evidence(platform admins) summarises the security controls in force.- Host ports: the base compose binds Postgres (5432), MinIO (9000/9001), the plain-HTTP nginx
web(8080) and the documentation nginxdocs(8081) to127.0.0.1; the api has no host port at all. Only Traefik reacheswebanddocs, overdokploy-network. Verify from off-site with a TCP connect to 5432/9000/8080/8081 after any compose change.