Connections
A connection says one thing: this app may use this service, at this level — the three parts are a site, one of the project’s databases, buckets or auth apps, and the level the connection grants. It is the single record behind everything the deployed app is handed: its connection variables, the network path that lets it reach a database at all, and the capabilities of its managed platform key.
The full model — the levels, what each one grants, and what a new service starts connected to — is in the Connections guide.
CLI v0.11.0 and newer print the variables a connection adds or removes.
On an older CLI the commands work the same, they just don’t list the names —
ghayma env pull always shows what the app receives. The read-only levels
(--level read-only on a database, --level read on a bucket) need
v0.11.1 or newer; the console sets them on any CLI.
Multi-site projects: connect and disconnect act on one site, and
connections can be narrowed to one with --site <slug> (slug or name).
Without it they use the site this directory is linked to, and at a
workspace manifest root they ask which
site you mean. See Sites.
ghayma connections
List the project’s connections: which site (app) may use which database, bucket
or auth app, and at what level. Without --site, every site of the linked
project is listed.
# Every site of this project
ghayma connections
# One site
ghayma connections --site admin
# The rows exactly as the API returns them
ghayma connections --jsonOutput:
🔗 Connections for my-project:
SITE KIND SERVICE LEVEL
admin database my-postgres read-only
main database my-postgres connect
main bucket uploads read-write
main auth app shop client| Column | What it holds |
|---|---|
SITE | The site’s slug — the app the connection is for |
KIND | database, bucket or auth app |
SERVICE | The service’s name, as the API lists it — a database’s or bucket’s name, an auth app’s app id |
LEVEL | read-only or connect, read or read-write, client or admin |
Rows are ordered by site, then by kind (databases, buckets, auth apps), then by
name. With nothing connected the command prints No connections yet. and the
ghayma connect line below it.
Options:
| Flag | Description |
|---|---|
--site | Only this site (name or slug) |
--json | Print the rows as JSON, exactly as the API returns them |
ghayma connect
Connect a site (app) to one of the project’s services. The app then receives the service’s variables, the network path to a database opens, and the app’s managed platform key gains what the connection implies — on the running deployment and at every deploy. The credential it is handed is its own: a database role or MongoDB user created for that one connection rather than the database’s main user, and an S3 access key created for that one connection rather than the bucket’s. See Each app gets its own database credential and Each app gets its own storage key.
ghayma connect database my-postgres
ghayma connect bucket uploads --site admin
ghayma connect auth shop --level adminKinds are database, bucket and auth; db and storage are accepted as
aliases for the first two. Names are what the API lists: a database’s or
bucket’s name, an auth app’s app id. A name that matches nothing is an error
listing the project’s services of that kind, and a project with none of that kind
is told how to create one — names are matched against the site’s own view, so a
name can never reach another project’s service.
The site is the one this directory is linked to, or --site.
Levels — every kind takes two, and omitting --level grants what a connection
has always granted:
| Kind | Levels | Default |
|---|---|---|
database | read-only, connect | connect |
bucket | read, read-write | read-write |
auth | client, admin | client |
A --level the kind does not accept fails before the request is sent.
# The app may read the database, and nothing more
ghayma connect database my-postgres --level read-only
# GET and LIST on the bucket; PUT and DELETE are refused
ghayma connect bucket uploads --level readA read-only connection injects the same variables and opens the same network path as a full one — only the credential is narrower. See Read-only connections.
auth ... --level admin prints the console’s note first: admin lets this app
manage all users of this auth app. At client the site’s managed key cannot
touch that app’s end users.
Running the command again with another --level changes the level of the
existing connection rather than adding a second one. A service already connected
at the level you asked for is left alone.
Output — the command names the variables the connection adds to that app:
✅ Connected database 'my-postgres' to 'main' (connect)
The platform now injects into 'main': DATABASE_URL, DATABASE_URL_MY_POSTGRES
Variables are live on the running app; pull them locally with: ghayma env pullNames only — no value is printed. The first database, bucket or auth app a site
is connected to also holds the flat names (DATABASE_URL here); every service
gets names suffixed with its own, as
Automatically injected variables
describes.
A level change reports both ends:
✅ Auth app 'shop' on 'main': level client → adminAnd an unchanged connection is not an error:
ℹ️ Database 'my-postgres' is already connected to 'main' (connect) — no change.Options:
| Flag | Description |
|---|---|
--site | Site (app) to connect, by name or slug |
--level | Access level (database: read-only or connect; bucket: read or read-write; auth: client or admin) |
ghayma disconnect
Disconnect a site (app) from one of the project’s services. Its variables leave the app, the network path to a database closes, and the app’s managed platform key loses what the connection implied. The credential created for that app is deleted with it — the database role or MongoDB user, or the bucket’s per-connection access key. The other apps, and the service’s own credential, are untouched.
ghayma disconnect database my-postgres
ghayma disconnect auth shop --site admin --yesThe command confirms first, naming the variables that will leave, unless
--yes:
⚠️ This stops 'main' using database 'my-postgres': DATABASE_URL, DATABASE_URL_MY_POSTGRES leave the app, and the network path closes. Continue? [y/N]Anything but y prints ❌ Cancelled. and changes nothing. On a yes:
✅ Disconnected database 'my-postgres' from 'main'Disconnecting something already disconnected is not an error — the command says so and stops, so a retry after a lost connection is safe.
Options:
| Flag | Description |
|---|---|
--site | Site (app) to disconnect, by name or slug |
--yes | Skip the confirmation |
ghayma connections rotate
Replace the credential one app holds for one service. The other apps using the same database or bucket keep theirs, and the service’s own shared credential is untouched. The level, the variable names and every other connection stay exactly as they were — only the secret moves. Why and when to do it: Rotating Credentials.
ghayma connections rotate database my-postgres
ghayma connections rotate bucket uploads --site admin --yesKinds are database and bucket (with the db and storage aliases). An
auth app has no per-connection credential, and the command says so before
sending anything — rotate its keys with
ghayma auth rotate-keys.
The app is the site this directory is linked to, or --site. The command
confirms first, unless --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 pullA service that is not connected to the app is not an error — the command
says there is nothing to rotate and stops. Two refusals name the way out: a
connection served by the service’s shared credential has nothing of its own
to rotate (rotate that one with ghayma db rotate or ghayma storage rotate),
and a database or bucket that cannot be reached leaves everything as it was, so
running the command again once it answers is safe.
Options:
| 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
a connection’s credential on any version.
ghayma access
Show who may reach one of the project’s services — the apps inside Ghayma that are connected to it, and the named principals outside Ghayma that hold their own credential for it.
An external principal is a connection minus the app: a name, a level, its own engine credential, an optional source-IP allowlist and an optional expiry. The model, the hostnames it hands out and the allowlist rules are in the External Access guide.
ghayma access database pg-main
ghayma access bucket uploads --jsonKinds are database and bucket, with db and storage accepted as
aliases. Names are the project’s own — a database’s or bucket’s name,
resolved inside the linked project, never another project’s same-named service.
External access is project-scoped, so no site is involved and --site is not
a flag here.
Output:
🔐 Access to database 'pg-main'
Apps in this project
APP LEVEL
main connect
External principals
NAME LEVEL ALLOWLIST EXPIRES LAST USED STATUS
metabase read-only 10.0.0.0/8 2026-12-01 — active
partner-x CI connect any source — — active
legacy-etl read-only any source — — revoked
ℹ️ 'partner-x CI' has no allowlist, so no IP filter is enforced at the front door while it is in force.
Give every principal an allowlist, or expect none of them to be filtered.| Column | What it holds |
|---|---|
NAME | The principal’s display name. A full principal id is accepted too, wherever a command takes a name |
LEVEL | read-only or connect on a database; read or read-write on a bucket |
ALLOWLIST | The source CIDRs, or any source |
EXPIRES | The date it retires itself, or — |
LAST USED | Empty (—) for databases in this release — their traffic never passes through the platform API |
STATUS | active, expired or revoked |
In-force principals come first, then alphabetically. No secret ever appears in a listing — there is no column for one and no field to fill it from.
With nothing there, the command names the way to start:
none — add one with: ghayma access add database pg-main --name <principal>An auth app has no external principals, and the command says so instead of calling anything:
❌ An auth app has no external principals — its external access IS its restricted project keys.
Create or revoke one in the console: Project → Settings → API keys (https://dash.ghayma.cloud).Options:
| Flag | Description |
|---|---|
--json | Print the rows as JSON (connections and external side by side) |
The whole ghayma access family needs CLI v0.11.4 or newer. The console
manages external access on any version.
ghayma access add
Grant a named principal outside Ghayma its own credential for one database or bucket. The credential is printed once and cannot be retrieved again.
ghayma access add database pg-main --name metabase
ghayma access add database pg-main --name "partner-x CI" --level read-only --allow 203.0.113.0/24
ghayma access add bucket uploads --name backups --expires 30Output — a database principal is handed a ready-made URL on this database’s own hostname:
✅ Added 'metabase' to database 'pg-main' (read-only)
Allowed sources: 10.0.0.0/8
Expires: 2026-12-18
🔑 Credential for 'metabase' on database 'pg-main'
URI: postgresql://c_1a2b3c4d:****@pg-main-a1b2c3d4.db.ghayma.tech:5432/db_main?sslmode=require
Username: c_1a2b3c4d
Password: ****
⚠️ this credential is shown once and cannot be retrieved again — store it now.
Lost it? Mint a new one with: ghayma access rotate database pg-main metabaseA bucket principal is an endpoint plus a key pair, not a URL:
🔑 Credential for 'backups' on bucket 'uploads'
Endpoint: https://s3.ghayma.tech
Bucket: st-uploads-a1b2c3d4
Access key: GK31c2f218a4b7
Secret key: ****A missing --name, and a --level the kind does not accept, are answered
locally — nothing is sent:
❌ A principal needs a name: ghayma access add database pg-main --name <principal>Options:
| Flag | Description |
|---|---|
--name | Name of the principal receiving the credential (required) |
--level | Access level (database: connect|read-only; bucket: read-write|read). Default: the kind’s full level |
--allow | Comma-separated source CIDRs or addresses. Default: any source |
--expires | Revoke the principal automatically after this many days. Default: no expiry |
Omitting --level grants the kind’s full level (connect,
read-write) — the same default a connection has. The console’s add form
starts at the weakest instead. Pass --level and neither can surprise you.
ghayma access allow
Replace the source addresses one principal may connect from. The list you pass is
the whole truth — it replaces whatever was there, and --set "" clears it.
ghayma access allow database pg-main metabase --set 203.0.113.0/24,198.51.100.7
ghayma access allow bucket uploads backups --set ""✅ Allowlist for 'metabase' on database 'pg-main': 198.51.100.7/32, 203.0.113.0/24✅ Allowlist cleared for 'metabase' on database 'pg-main' — any source may use its credential.Bare addresses are accepted and widened (203.0.113.4 becomes
203.0.113.4/32), and the server canonicalises, dedupes and sorts the list — so
the line you get back is what is actually stored. At most 50 entries per
principal.
The flag is never implied: leaving --set out entirely is refused rather than
read as “clear it”.
❌ Pass the whole list with --set (an empty --set "" clears it): ghayma access allow database pg-main metabase --set <cidr,...>After the change the command re-states the collapse rule whenever it applies — while any principal of the service is unrestricted, no IP filter is enforced for any of them.
Options:
| Flag | Description |
|---|---|
--set | The whole allowlist, comma-separated. An empty --set "" clears it (required) |
ghayma access rotate
Replace a principal’s secret without moving anything else: the name, the level and the allowlist stay as they are, so the consumer swaps one string and carries on. The old secret stops working immediately.
ghayma access rotate database pg-main metabase
ghayma access rotate bucket uploads backups --yesThe command confirms first, unless --yes:
⚠️ This replaces the secret 'metabase' uses for database 'pg-main' now: the old one stops working immediately and whatever holds it has to be updated. Continue? [y/N]On a yes it prints the new credential exactly as access add does — once.
✅ Rotated 'metabase' on database 'pg-main'This is also the answer to a lost secret: nothing reads one back, so a replacement is a rotation.
Options:
| Flag | Description |
|---|---|
--yes | Skip the confirmation |
ghayma access revoke
Drop the credential one principal holds and close its row. The engine credential is deleted: anything still connecting with it stops at once, and it cannot be restored — the replacement is a new principal with a new secret.
ghayma access revoke database pg-main metabase
ghayma access revoke bucket uploads backups --yes⚠️ This deletes the credential 'metabase' uses for database 'pg-main' now: anything still connecting with it stops immediately, and it cannot be restored. Continue? [y/N]✅ Revoked 'metabase' on database 'pg-main' — its credential no longer works.The row itself is kept, marked revoked, so who reached this database, and when did it stop still has an answer. Revoking something already revoked is not an error:
ℹ️ 'metabase' is already revoked on database 'pg-main' — no change.On a PostgreSQL database, revoking the last active principal also removes its per-database hostname — the front door closes, it is not just re-passworded.
Options:
| Flag | Description |
|---|---|
--yes | Skip the confirmation |
ghayma connect --local
Run the app on this machine against the project’s real databases. --local
opens a tunnel session for the site, stands a loopback listener in front of
every database the site is connected to, and writes the site’s effective
variables with those addresses swapped in — so the app you run here is the
deployed app, talking to the same data. Nothing is exposed to the internet.
# Tunnel the linked site's databases and write .env.local
ghayma connect --local
# A specific app, and a file of your own
ghayma connect --local --site admin --out .env.development.localIt takes no arguments — the app is the one this directory is linked to, or
--site.
Output:
🔌 Tunnel open for 'main' — its databases answer on this machine:
my-postgres (postgres) → 127.0.0.1:15432
analytics (mongodb) → 127.0.0.1:15017
Wrote 12 variables to .env.local (hosts point at the listeners; restore with: ghayma env pull)
Leave this running; Ctrl-C closes the tunnel.Leave it running while you develop. Ctrl-C closes the listeners, gives the
session back, and prints ✅ Tunnel closed.
What it binds. One listener per connected database, on 127.0.0.1 only:
PostgreSQL counts up from 15432, MongoDB from 15017, stepping over a port
something else already holds. Buckets and auth apps need no tunnel — they are
public HTTPS with credentials, so your machine already reaches them. A site
connected to no database is told to connect one first.
The dotenv file is .env.local next to the app — its directory in a
workspace — or the path you give with
--out. Only each database’s host:port changes; the credentials, the database
name and every option in a connection string ride along untouched. The file goes
through exactly the same protections as
ghayma env pull:
- a file git tracks is refused outright;
- inside a repository, a file git does not ignore is refused too unless you
pass
--force; - the file is written readable by you only;
- no value is ever printed, and the session’s token never reaches the file.
Those addresses answer only while the command runs. The file left behind
names 127.0.0.1 ports that stop answering the moment you press Ctrl-C, so it
is not a file to deploy or to keep. Run
ghayma env pull to write the cluster
addresses back.
Opening a tunnel needs the project admin role — the session reaches the
databases with the app’s own credentials, the same bar as env pull. A session
lasts 8 hours, and both opening and closing it are recorded in the project’s
audit log: which site, which project, how many databases, from where — never the
token, never a value. A session that expires while you work ends the command
with one line telling you to run it again.
Options:
| Flag | Description |
|---|---|
--site | Site (app) to tunnel, by name or slug |
--out | File to write (default: .env.local next to the app) |
--force | Write even if the file is not git-ignored (a tracked file is always refused) |
connect --local needs the CLI release that carries it. The platform side is
live; if ghayma connect --help does not list --local, update the CLI.
ghayma env pull
Writing a site’s effective variables — the ones you set plus every value the
platform derives from its connections — into a local dotenv file is
ghayma env pull, on the Environment Variables
page.
Relationship to ghayma db sites
ghayma db sites reads and writes the same
records from the other side: what it lists is the project’s database connections,
and what --add makes is one at the default level, connect. It keeps working
exactly as before — ghayma connections and ghayma connect are the newer form,
the only one that also covers buckets and auth apps, and the only one that sets a
level.