Credential rotation
What actually rotates, when the old password stops working, and which parts of your stack update themselves.
What rotates
A plain rotation replaces exactly one thing: the database password for the project's role. A project's runtime role (app_user), where it has one, rotates separately and is not touched. The hostname, ports, database name, and role name all stay the same - only the password segment of your connection URLs changes. API keys, webhook secrets, and integration tokens have their own rotation surfaces and are untouched.
A rotation with a grace period (grace_hours in the request body, --grace-hours on the CLI, up to 720) issues a new username and password instead. The outgoing credential keeps authenticating until the window ends - Postgres itself enforces the deadline - so running deployments can drain onto the new URLs, and the old role is dropped afterwards. Both usernames act as the same database owner, so nothing about object ownership changes.
The new password is generated server-side (a 24-byte random secret), stored AES-GCM encrypted in the control plane, and applied to your database with an ALTER ROLE. It is never chosen by, or shown to, anyone before it lands in your connection details.
Triggering a rotation
Rotating takes the organization admin role in the dashboard, or an organization-wide API key (the CLI, Terraform, CI). A workspace member without the admin role cannot rotate: a rotation without a grace period drops every live connection on the old password.
From the dashboard (the project's Connections tab or Settings → Rotate Credentials) or the API:
curl -X POST https://capydb.dev/api/capydb/v1/projects/$PROJECT_ID/credentials/rotate \
-H "Authorization: Bearer $CAPYDB_API_KEY"From the CLI:
capydb credentials rotate --waitIt asks you to confirm (--confirm skips the prompt), and capydb connection-string hands you the fresh URLs once the rotation completes. See the CLI reference for its flags.
Rotation is asynchronous: the response is a job, the project moves to a rotating_credentials state, and flips back to ready when the job completes. Poll GET /v1/jobs/{id} or listen for the events below. The job retries up to 3 times on transient failure; if it fails terminally, the old password remains valid and the project returns to ready.
Invalidation timing
- Until the job completes, the old password keeps working. Rotation is requested, not instant.
- Once the job completes, new connection attempts with the old password fail - on both the direct and pooled endpoints simultaneously, since both authenticate against the same database role. With a grace period, the old username and password keep working until the window you chose ends, then fail the same way.
- Already-open connections are not terminated. A session established before the rotation keeps working until it disconnects on its own. If "no session may outlive the old credential" is a hard requirement (a leaked URL, an offboarding), rotate and then restart your app processes so every pool reconnects with the new password.
What updates itself
- Connected Vercel and Netlify integrations re-push
DATABASE_URL/DATABASE_URL_UNPOOLEDautomatically after the rotation job succeeds (the sync itself retries up to 5 times). Note that already-running deployments baked the old env vars at build/boot time - trigger a redeploy to pick up the new URLs. - A
credentials.rotatedwebhook event fires (plus the genericjob.completed), so your own automation can react. - The audit trail records
project.credentials_rotatedwith the job ID and role name.
Everything else - CI secrets, local .env files, password managers, that one cron job on a forgotten VM - holds the old URL until you update it.
Runbook
- Pause anything that auto-restarts database workers mid-deploy, if reconnect storms matter to you.
- Trigger the rotation (dashboard or
POST /v1/projects/{id}/credentials/rotate). - Wait for completion: poll the job, or act on the
credentials.rotatedwebhook. - Fetch the new URLs: dashboard Connections tab,
GET /v1/projects/{id}/connections, orcapydb connection-string. - Update every consumer that is not a connected Vercel/Netlify integration: CI secrets, other deploy targets, local env files.
- Redeploy / restart app processes - both to pick up pushed env vars and to drop any sessions still authenticated with the old password.
- Confirm: new connections succeed, and the audit trail shows the rotation.
When to rotate
- A connection URL leaked - committed to a repo, pasted in a chat, logged somewhere.
- Someone with database access left the team.
- Routinely, if your security policy wants credential turnover; rotation is cheap and does not touch data.
Type generation
Generate TypeScript types, Zod schemas, a Drizzle schema, Go structs, or Python models from the live database schema - and gate CI on schema drift.
Runtime role (split role model)
A second login per project, app_user, that owns nothing and cannot bypass row-level security - so your RLS policies apply to request traffic while the owner keeps running migrations.