Runbook
Rotate the production database credentials
Quarterly rotation of api_rw on prod-pg-1. Zero downtime —
but only if step 4 finishes before you touch step 5.
- When
- Quarterly
- Takes
- ~15 min
- Downtime
- None
- Needs
- db-admin
Before you start
All four. They take a minute together.
-
You are on the VPN and reach the primary.
psql -h prod-pg-1 -c '\conninfo' -
The deploy freeze is off.
./bin/freeze status # => open -
No migration is running.
./bin/migrations running # => 0 -
You can write to the secret store.
vault kv get -field=version \ secret/prod/db
Stop if any is false Rotating during a migration leaves the migrator holding a dead connection. Wait it out.
The procedure
Two roles exist at once for a few minutes. That overlap is the design — nothing restarts, nothing has to be timed.
-
Announce it
A rotation nobody expected costs more than the one you announce.
./bin/announce "rotating api_rw, ~15 min, no downtime expected"posted to #eng-oncall -
Generate the new secret
Written to a staging slot the application does not read yet.
export NEW=$(openssl rand -hex 24)vault kv put secret/prod/db/next \ password="$NEW"Key Value version 8 -
Create the successor role
It inherits from the
api_rwgroup role, so you never re-grant table by table.psql -h prod-pg-1 \ -f sql/rotate.sql \ -v pw="$NEW" -v role=api_rw_q3CREATE ROLE GRANT -
Point the application at it
This is the step that matters. The sidecar reloads on its own, no restart. Until it returns, the old role is still serving traffic.
vault kv put secret/prod/db \ username=api_rw_q3 \ password="$NEW"./bin/wait-for-rollout \ --key secret/prod/db \ --timeout 120sall 14 pods on version 9 (47s)
Verify — between steps 4 and 5
Zero on the old role for five straight minutes. That is the bar.
psql -h prod-pg-1 -c "select usename,
count(*) from pg_stat_activity
where datname='app' group by 1" api_rw_q3 | 42
api_rw_q2 | 0
If the old role still has connections: a pod missed the reload. Restart it.
-
Retire the old role
Only once the verification check has been clean for five minutes.
psql -h prod-pg-1 \ -c "drop role api_rw_q2"DROP ROLEIf it says objects depend on it: something still owns tables as that role. Stop and escalate — never
REASSIGN OWNEDon production alone.
If it goes wrong
Rollback — safe at any point before step 5
The old secret version and the old role both still exist, so this is a sixty-second undo — not a deploy.
vault kv rollback -version=8 \
secret/prod/db./bin/wait-for-rollout \
--key secret/prod/dball 14 pods back on version 8 (39s)
When to escalate
-
Connections have not drained after 10 minutes.
Page
/page data-platformand leave both roles in place. -
DROP ROLEfails for any reason. Stop and page. Two live roles is safe; half-rotated is not.