Connections
A connection says one thing: this app may use this service, at this level. It is the single record behind everything a deployed app is handed — its database URL, its bucket credentials, its auth variables, its platform key — and, for a database, the network path that lets it open a connection at all.
A service is not handed to every app of its project. An app reaches a database, a bucket or an auth app once you connect the two, and stops reaching it the moment you disconnect them.
What can be connected
The connectable services are a project’s databases, storage buckets and auth apps. The other end is always a site — one deployable app of the project. The level says how much the connection grants:
| Service | Level | What the app gets |
|---|---|---|
| Database | read-only | Everything connect gives, except that the app’s own credential may only read |
| Database | connect | DATABASE_URL / MONGODB_URI / REDIS_URL — with the MONGODB_URL and VALKEY_URL aliases and the per-database names — plus the network policy that lets its pods, and its builds, reach the database |
| Bucket | read | Everything read-write gives, except that the app’s own key may only read the bucket |
| Bucket | read-write | STORAGE_ENDPOINT, STORAGE_ACCESS_KEY, STORAGE_SECRET_KEY, STORAGE_BUCKET, STORAGE_REGION |
| Auth app | client | GHAYMA_AUTH_APP_ID, GHAYMA_AUTH_URL, GHAYMA_AUTH_PUBLIC_URL and GHAYMA_AUTH_SERVER_KEY, with the app id passed to the build as well |
| Auth app | admin | Everything client gives, and the site’s managed GHAYMA_API_KEY may administer that auth app’s end users |
A Valkey is the one database a build never reaches: its variables and its network path are runtime only (see Valkey).
Every kind has two levels, listed weakest first. A connection made without a
level grants what a connection has always granted — connect for a database,
read-write for a bucket, client for an auth app — so the read-only levels are
something you ask for. See Read-only connections.
The managed platform key follows the levels
A site connected to a bucket, a database, or an auth app at admin is also
handed GHAYMA_API_KEY, a project API key the
platform mints for that site. It reaches exactly what the site is connected
to, so a project’s second bucket, database or auth app is never swept in: each
bucket at its connection’s level, each database to read it, and each auth app
connected at admin to administer its end users. Any other one answers 403
with code not_connected, and an object write through a read bucket
connection answers 403 with code read_only_connection. A site whose
connections imply no access — none at all, or only an auth app at client —
gets no key, and any key it still holds is revoked. Deleting the site revokes
its key too.
When a connection applies
Connecting, disconnecting or changing a level reaches the running apps right away — the pods pick up the new values without a redeploy, and a database network policy converges within about two minutes. Every later deploy derives the same thing again, so a rollback gets exactly what a deploy gets.
All of these variables are derived, never stored. They do not appear in
ghayma env list, there is nothing to edit, and a value you set yourself
under one of these names is replaced by the platform’s. See
Automatically injected
variables.
A database or bucket connection always holds a credential of its own — nothing falls back to the service’s own credential. A connection that has none yet gets no values at deploy time: a deploy carries no value under its variable names. An app that is already running keeps the values it has under those names until the platform gives the connection a credential of its own. The platform retries every ten minutes, as soon as the service runs, and then restarts that app with it.
A deploy of a site with such a connection first tries to give it one. If it still cannot, the deploy stops before building, naming the service and the site:
Database "shop-db" has no credential of its own yet for site "main" — retry in a minute, or reconnect it.A bucket’s line starts with Bucket. ghayma env pull and ghayma connect --local leave such a connection’s
variables out of the file they write.
Each app gets its own database credential
An app connected to a PostgreSQL, MongoDB or Valkey database is never
handed the database’s own login. The platform creates a credential for that one
connection — named c_ followed by eight characters, like c_14bb37f4 — and
that credential is what the app’s DATABASE_URL, MONGODB_URI or REDIS_URL
carries.
- Disconnecting drops only that app’s credential. Every other app on the database, and every named principal, are untouched.
- Migrations still work across apps. On PostgreSQL every app connected at
level
connectacts as one shared owner role —<dbname>_rw— so a table created by one app’s migrations can be altered by another app’s. Creating tables, altering tables and types, creating schemas and creating trusted extensions all work as before. - Rotating is per app. To re-secret one app’s credential, see Rotating Credentials; the other apps keep theirs. The database’s own login is never handed out, and only an operator rotates it.
On MongoDB the c_… user holds the readWrite role on that one database and
nothing else, and it lands in MONGODB_URI and its MONGODB_URL alias.
Nobody is shown the database’s own login — not you, not a collaborator.
ghayma db credentials and the console
show its host and port and which apps receive which variables, without any
password. From your laptop,
ghayma connect --local tunnels the
site’s databases and writes the app’s own credentials into .env.local, so an
app you run on your machine uses the same credential the deployed one does. A
system outside Ghayma gets its own too — a named
principal is the same c_… identity, with a level, an
optional source allowlist and its own revoke.
A connection is made only with a credential of its own. Connecting an app
to a database that is stopped or still being provisioned is refused with 409
and code: not_running — start it, or let it finish, then connect again. A
service that cannot give an app a credential of its own refuses with 409 and
code: credential_unsupported, and the connection is not made. A Valkey that
is not running answers code: database_not_running (see
Valkey). MongoDB always runs as a
single-node replica set, so every MongoDB app gets a user of its own.
Each app gets its own storage key
An app connected to a bucket is handed an S3 access key created for that one
connection. It arrives in STORAGE_ACCESS_KEY and STORAGE_SECRET_KEY — and in
the suffixed STORAGE_<NAME>_* names — never the bucket’s own key.
- That key reaches one bucket. It reads and writes that bucket and nothing else, and it can never delete the bucket or change its settings.
- Disconnecting deletes only that app’s key. The other apps, and every named principal, are untouched.
- Rotating is per app. To replace one app’s key, see Rotating Credentials; the other apps keep theirs.
Nobody is shown the bucket’s own key.
ghayma storage credentials and the
console show the bucket’s S3 endpoint and name and which apps receive which
variables, without any key. From your laptop,
ghayma env pull writes your site’s
STORAGE_* variables — the S3 endpoint is public, so they work from your
machine as they are. A tool outside Ghayma gets its own key too — a named
principal is a key pair of its own, at read or
read-write, revocable alone.
Connecting an app to a bucket that is still being provisioned is refused, as for
a stopped database, with 409 and code: not_running: connect it once the
bucket is active.
Read-only connections
A database connection can be read-only, and a bucket connection can be read.
The app keeps the same credential — the same name, the same password or key —
and only what it may do changes:
- PostgreSQL. The app’s role may
SELECTeverything the read-write apps’ shared owner group holds, including tables another app creates later. NoINSERT,UPDATE,DELETEor DDL. - MongoDB. The app’s user holds the
readrole on that database instead ofreadWrite. - Valkey. The app’s user may run reads and
SUBSCRIBE, and nothing that writes. - Buckets. The app’s key may
GETandLIST;PUTandDELETEare refused.
The level changes nothing else. The network path to the database is the same, and
so is the set of injected variables — a read-only app still receives the full
DATABASE_URL or STORAGE_* set, its credential simply cannot write.
Connecting without a level is unchanged: a database connection starts at
connect and a bucket connection at read-write. Pick the read-only level when
you connect, or change it afterwards — the console and the CLI both change a
level in place, without disconnecting first.
ghayma connect database my-postgres --level read-only
ghayma connect bucket uploads --level readManage them in the console
There are two views of the same rows in the console — one per app, one per service. Use whichever matches the question you are asking.
What this app uses
Open Project → the site → Connections:
- Connected services lists the databases, buckets and auth apps this app may use, each with its level and, as chips, the variables the platform injects for it. Change a service’s level here, or Disconnect it.
- Connect a service lists the project’s other services, each with the variables connecting would add — pick a level, then Connect.
Databases and buckets carry the level picker on connected rows and in the Connect dialog, exactly as auth apps always have.
Those chips are variable names only; no value is ever shown. Read the values
with ghayma env pull.
Which apps use this service
The Access section on the database settings page, the bucket page and the auth app page lists every app of the project, connected or not, with the level of each connection. Connect, disconnect and change a level from there.
On a database or a bucket the same table also carries the named
principals outside Ghayma — rows badged external
rather than app — with their allowlist, expiry and status, and the actions to
add, rotate, revoke or re-allowlist one. An auth app’s external access is its
restricted project keys, so its Access list
shows those instead.
Raising an auth app to admin asks for confirmation, and the control says why:
admin lets this app manage all users of this auth app. Disconnecting asks
for confirmation too, and names the variables that will disappear from the
app.
Seeing connections needs the read role on the project; connecting, disconnecting and changing a level need write or above.
How a service gets connected
Nothing is connected unless you choose it. A new database, bucket or auth app is connected to the sites you pick when you create it, and to none if you pick none — even in a project with a single site. Creating a site, a project’s first one included, connects nothing either: a new site starts with no connections.
You choose in three places:
- When you create the service. The console’s create form lists the
project’s sites under Connect to sites: a lone site is ticked for you,
several start unticked, and a project with no site yet notes that you can
connect the service after the first deploy. The CLI takes
--site <slug>(repeat it for several) or--no-connect, and asks on a terminal — see Connecting it to a site. The API takesconnect_site_ids. - When you deploy.
ghayma deployoffers each service no site uses to the site it deploys, before the build — see Services no site uses yet. - Any time after. From the site’s Connections tab, the service’s
Access list, or
ghayma connect.
ghayma db create shop-db --site main # connected to 'main' as it is created
ghayma storage create media --no-connect # connected to nothing for nowA service no site uses is pointed out: the console shows a banner on the project — 2 services aren’t connected to any site — linking each one to its Access list, and an empty Access list reads Not connected to any app yet — pick the apps that should use it.
A new PostgreSQL database connects once it accepts logins
A new PostgreSQL database accepts logins a moment after it is created — usually
within a minute. The sites you chose wait for it: the database’s page shows
Connecting to main…, ghayma db sites
marks them ⏳, and the API lists them in the database’s pending_connections.
They connect as soon as it is ready, each with its own credential. If they still
haven’t connected after 15 minutes, the database’s connect_error says why.
A new Valkey works the same way: the sites you chose connect once it is running.
Connecting it before then is refused with 409 and code: database_not_running
(see Valkey).
From the API
The routes live under /api/v1 and take an account token or a dashboard
session. Reading needs the project read role, every mutation needs
write.
| Route | Does |
|---|---|
GET /projects/:id/connections | Every connection in the project. Filter with site_id, kind, resource_id |
GET /projects/:id/sites/:siteId/connections | One site’s connections, plus the services it could still be connected to |
PUT /projects/:id/sites/:siteId/connections | Replace a site’s whole set |
POST /projects/:id/sites/:siteId/connections | Connect one service, or change its level |
DELETE /projects/:id/sites/:siteId/connections/:kind/:resourceId | Disconnect one service |
GET /projects/:id/connections/unconnected | The project’s services no site is connected to |
Creating a database, bucket or auth app takes connect_site_ids, the sites to
connect it to — see Create Database.
A row is the same everywhere:
{
"site_id": "…",
"site_slug": "admin",
"kind": "auth_app",
"resource_id": "…",
"resource_name": "main-auth",
"level": "client",
"env_names": ["GHAYMA_AUTH_APP_ID", "GHAYMA_AUTH_URL", "…"],
"created_at": "2026-09-08T10:00:00Z"
}env_names is the exact set of variable names the platform injects into that
site for that connection — names, never values. The services a site could still
be connected to carry it too, so a UI can say what connecting would add. See
Connections.
The older per-service routes — GET/PUT /databases/:id/sites and
GET/PUT /storage/:id/sites — keep working and read and write these same
connections. Full reference: Connections.
A project API key cannot call these routes — they answer 403. A site’s
managed key is minted from its own connections, so a key able to change them
could widen what it may do.
From the CLI
ghayma db sites works exactly as before —
what it lists and edits is now a database connection, and --add makes one at
the default level, connect:
ghayma db sites my-postgres # which apps may use it
ghayma db sites my-postgres --add admin # connect the 'admin' appThe dedicated commands cover all three kinds, and read and write these same rows:
# Which apps may use which services
ghayma connections
ghayma connections --site admin
# Connect a service to an app — without --level, the kind's usual level
ghayma connect database my-postgres
ghayma connect bucket uploads --site admin
ghayma connect auth shop --level admin
# Connect at a read-only level, or move an existing connection down to one
ghayma connect database my-postgres --level read-only
ghayma connect bucket uploads --level read
# Stop an app using a service (asks first, unless --yes)
ghayma disconnect auth shop --site admin
# Write the site's effective variables — the derived ones included — to .env.local
ghayma env pull
# Tunnel the app's databases to this machine and write .env.local pointing at them
ghayma connect --localFull reference: Connections and ghayma env pull.