Rotating Credentials
Every app connected to a database or a storage bucket holds its own
credential for it — a database role named c_ plus eight characters, or an S3
access key created for that one connection. Rotating replaces the secret behind
one of those credentials, and nothing else.
The connection itself is unchanged: same app, same service, same level, same variable names. See Connections for how an app comes to hold a credential at all.
When to rotate
- A value leaked — printed into a build log, committed, pasted into a chat, or pulled onto a laptop that is no longer yours.
- Someone who could read the app’s variables left the project.
- Hygiene — you would like the secret an app has been using for a year to stop working.
What rotation changes
One connection. The other apps using the same database or bucket keep their own credentials, and the service’s own credential — a database’s own login, a bucket’s own key — is not touched.
The identity and its grants stay. On PostgreSQL, MongoDB and Valkey the app’s role or user is re-passworded in place: the same name and the same privileges, and on PostgreSQL the same ownership of anything it created. On a bucket a replacement access key is issued with the same permissions on the same bucket, and the key it replaces is deleted about a minute later — so an app still signing requests with the old key keeps working until the new one reaches it.
The app picks it up without a redeploy. The platform patches the new value onto the running app straight away, which restarts its containers with it. Every later deploy derives the same value anyway, so a redeploy is a backstop, never a step you have to take.
A local .env.local keeps the old value. Anything you ran from a file
written by ghayma env pull or
ghayma connect --local is
authenticating with a secret that no longer exists — pull again.
Rotating is safe to repeat: a second rotation simply issues another secret.
Rotate in the console
Open your project → the site → Connections, find the service in Connected services and click Rotate credential. The button arms for five seconds and says what it will do:
Changes this app’s database credential now and restarts it with the new value. Level, variable names and other apps stay as they are.
Click Confirm rotate. The row reports Credential rotated.
The same control sits on the other side of the relationship: Rotate on an app’s row in the Access list of a database or a bucket rotates that app’s credential for the service, without leaving the service.
Rotating needs the project write role — the same bar as connecting the service in the first place.
Rotate from the CLI
# This app's credential for a database
ghayma connections rotate database my-postgres
# A specific app's bucket key, without the confirmation
ghayma connections rotate bucket uploads --site admin --yesThe app is the site this directory is linked to, or --site. The command
confirms first unless you pass --yes:
⚠️ This replaces the credential 'main' uses for database 'my-postgres' now: the running app is updated immediately (its pods restart with the new value), and anything using a locally pulled .env.local needs 'ghayma env pull' again. Continue? [y/N]Anything but y prints ❌ Cancelled. and changes nothing. On a yes:
✅ Rotated the credential of database 'my-postgres' for 'main'
Variables are live on the running app; pull them locally with: ghayma env pullOptions:
| Flag | Description |
|---|---|
--site | Site (app) whose credential to rotate, by name or slug |
--yes | Skip the confirmation |
ghayma connections rotate needs CLI v0.11.2 or newer. The console rotates
on any version.
Changing the level is not a rotation
Moving a connection between read-only and connect, or between read and
read-write, keeps the credential — a level is a grant, not an identity, so
nothing is re-issued and the app is never handed a new password. Only what it
may do changes, and it changes on the running app the same way. See Read-only
connections.
When there is nothing to rotate
| You see | Why | What to do |
|---|---|---|
| the service is not ready: start it (or let it finish provisioning), then try again… | The database is stopped, or the database or bucket is still being provisioned: 409 with code: not_running | Nothing was changed. Start it, or let it finish, then run the same command again |
| this connection has no credential of its own yet, so there is nothing to rotate… | The connection is waiting for its own credential: 409 with no code | Wait: the platform gives it one as soon as the service runs, within ten minutes. Or reconnect the app — disconnect it, then connect it again |
| …could not be reached… | The database or bucket did not answer just then | Nothing was changed. Run the same command again once it answers |
| …is not running, so this app’s password cannot be rotated now… | The database is a Valkey that is stopped or still starting: 409 with code: database_not_running | Start it if it is stopped, or wait until ghayma db info shows it running, then run the same command again |
| No Rotate credential button on an auth app | An auth app has no per-connection credential | Rotate its signing keys with ghayma auth rotate-keys, or its server key from the auth app’s page |
The service’s own credential
A database’s own login and a bucket’s own key are the platform’s. They are
never handed out — not to you, not to an app, not to a tool outside Ghayma —
and only an operator rotates them, so there is nothing for you to rotate at the
service level. Every app holds a credential of its own, and every named
principal outside Ghayma has its own too, rotated with
ghayma access rotate. A database or
bucket credential that leaked from an app or a tool is one of those, and
rotating it is the fix.
When an operator rotates a bucket’s own key, presigned URLs signed before it keep working until they expire: the replaced key retires after 7 days. An incident rotation stops them at once.
Related
- Connections — what an app is handed, and at what level
- Connections — the full
ghayma connectionscommand reference ghayma env pull— read what an app actually receives