Skip to Content

Sites

A site is an independently deployable app within a project. Each site has its own deployments, domains and environment variables, and uses the project’s databases, buckets and auth apps through the connections you give it.

Example use cases:

  • frontend + admin + api in one project
  • A monorepo where each package is its own site
  • A/B deployments or staging environments

A site also carries an environment kind — production, staging or development — which is what makes a deploy a production one, what lets a site inherit the default site’s variables, and what a promotion moves between. See Environments.

Site URLs

Every site is served at its own .ghayma.app URL, and the rule that picks it has exactly two cases:

Site slugHosted at
main<project-slug>.ghayma.app
anything else<project-slug>-<site-slug>.ghayma.app

So project acme with sites main, admin and api serves acme.ghayma.app, acme-admin.ghayma.app and acme-api.ghayma.app.

You never have to derive this yourself — every site the API returns carries a computed default_domain field. See Sites in the API reference.

Custom domains are attached per-site on top of this URL and are never affected by renames — see Domains.

How sites work

A brand-new project has no sites at all. The main site is created lazily, on your first app action — the first ghayma deploy, or attaching a custom domain. If you only have one app, you never need to think about sites: everything lands on main.

main is not special beyond its URL. It can be renamed and it can be deleted, and a project holding several custom sites with no main at all is a perfectly normal steady state.

When you run ghayma init and choose to deploy an app from the directory, you’re asked for a site name. Press Enter to use main, or type a name like frontend. Answer No site instead and the project starts with none — see Projects without a site.

The .ghayma.json config file stores the active site:

{ "project_id": "uuid", "name": "my-project", "slug": "my-project", "framework": "nextjs", "site_id": "uuid", "site_name": "frontend", "site_slug": "frontend" }

Every deploy, env, and domain command run in that directory targets the site stored in this file.

That is the single-app layout: one directory, one site. A workspace repo can instead hold one manifest at its root listing every site and the directory it builds from — then you pick the site per command with --site <slug>, or just run the command inside the app’s own directory. See Workspaces (monorepos).

When no site is named

If a command or API call names a project but no site, the platform walks a fixed ladder to decide which site you meant:

  1. The project’s default site, if one is set
  2. Otherwise the site named main
  3. Otherwise the only site, if the project has exactly one
  4. Otherwise it refuses and asks you to name a site or set a default

Rung 4 is a real outcome, not an edge case: once you have two custom sites and no main, nothing is implicit any more. Set a default from the Dashboard, or pass the site explicitly (ghayma site use <slug>, or --site where the command accepts it).

The one exception is a project with no sites at all — there, a first deploy or a custom-domain attach creates main rather than failing. An ambiguous project never gets a site invented for it.

A new database, bucket or auth app does not use this ladder. It is connected only to the sites you choose when you create it — none if you choose none, even in a project with one site — and creating a site connects nothing. ghayma deploy offers the services no site uses to the site it deploys. See How a service gets connected.

ghayma site list

List all sites in the current project. The active site (from .ghayma.json) is marked with ▶.

ghayma site list

Output:

📌 Sites: ▶ frontend (slug: frontend, env: development, status: active, id: abc123...) [inherits env] admin (slug: admin, env: production, status: active, id: def456...) main (slug: main, env: production, status: active, id: ghi789...) To switch active site: ghayma site use <slug>

env: is the site’s environment kind, and [inherits env] marks a site that resolves its variables through the default site’s — see Environment variables are inherited.

ghayma site create

Create a new site in the current project.

ghayma site create frontend

Or run without arguments for an interactive prompt:

ghayma site create

Output:

✅ Site 'frontend' created (slug: frontend, id: abc123...) Environment: development · inherits the default site's variables (override any of them with 'ghayma env set') To deploy to this site, switch to it first: ghayma site use frontend ghayma deploy

Site names are slugified automatically. "My API" becomes slug my-api.

Options:

FlagDescription
--envEnvironment kind of the new site: development (default), staging or production. dev, stage and prod are accepted too
ghayma site create dev --env development ghayma site create admin --env production

A new non-production site also starts with environment-variable inheritance on: it resolves the project’s default site’s variables as its base and overrides them key by key, so standing one up is not a hand-copy of production’s list. Credentials are never inherited — a development site gets its own DATABASE_URL through its own connection. See Environments.

An unknown kind is rejected before the request is sent: ❌ unknown environment "developement" — use production, staging or development.

In a linked workspace, site create offers to add the new site to the root manifest and asks which directory holds it — skip that and the site is created but left out of the manifest until you add it yourself or re-run ghayma link. See Workspaces (monorepos).

Two names are rejected up front: one that slugifies to a site you already have (409), and one so long that the site’s Kubernetes namespace would exceed 63 characters (422).

A new site starts with no build settings — it auto-detects until you deploy a .ghayma.json or fill them in on the site’s Settings page. Nothing is copied from a sibling site.

The CLI creates on the plan’s default tier. site create has no sizing flag yet — resize afterwards with ghayma site scale. The console’s New Site form and the API take the tier and replica count at create instead, which is one points admission rather than two.

ghayma site use

Switch the active site in .ghayma.json. All subsequent deploy, env, and domain commands will target this site.

ghayma site use admin

Output:

✅ Active site switched to 'admin' (slug: admin) Run 'ghayma deploy' to deploy to this site

You can switch by slug or by name. Switching preserves the rest of the file, including dockerfile_path and crons.

Not available at a workspace manifest root. A manifest has no single active site to switch — every command either takes --site or asks. Run site use inside the app’s own directory, or name the site per command: ghayma deploy --site admin --prod. See Workspaces (monorepos).

ghayma site environment

Change which environment a site is — production, staging or development.

ghayma site environment staging staging ghayma site environment preview development

Output:

✅ 'staging' is now a staging site (was development) It applies from the next deploy of this site.

The site is positional and required: re-kinding changes what that site’s deploys count as and who may touch them, so it is never inferred from the directory you happen to be in. A site that is already that kind is left alone — ℹ️ 'staging' is already a staging site — no change.

A deployment counts as production exactly when the site it targets is a production site. The kind also decides how the CLI and the dashboard warn about sharing a database across environments.

The project’s default site is always production and cannot be re-kinded — it is the site served at the project’s bare URL: the default site is always the production environment and cannot be changed; make another site the default first. Create another site for the environment instead, or make a different site the default first.

ghayma site inherit-env

Turn environment-variable inheritance on or off for a site.

ghayma site inherit-env dev on ghayma site inherit-env dev off

Output:

✅ 'dev' now inherits the default site's variables, overridden by its own See what it resolves to: ghayma env list --site dev

With it on, the site resolves the project’s default site’s variables as its base, overridden key by key by its own — set a variable of the same name here to override an inherited one. New non-production sites start with it on.

With it off, the site resolves its own rows only. Nothing is copied down first:

✅ 'dev' no longer inherits variables — it resolves its own rows only Anything it was inheriting stops being injected on its next deploy; nothing was copied down.

Credentials are never inherited whatever this is set to: database URLs, bucket keys, cron secrets, the auth variables and the managed platform key are derived per site from that site’s own connections. A development site inheriting production’s variables still uses its own database. See Environments.

The default site cannot inherit — it is the base the others read from: the default site is the base of the environment ladder and cannot inherit; the sites that inherit FROM it are the secondary ones.

ghayma site scale

Change an app’s compute tier and/or replica count — both priced in points. Both flags are optional: an omitted --tier keeps the current tier, an omitted --replicas keeps the current count. Scales the project’s active or only site unless you pass --site.

# Bigger tier, same replicas ghayma site scale --tier c # More replicas, same tier ghayma site scale --replicas 3 # Scale a specific site by tier and replica count ghayma site scale --site api --tier b --replicas 2

Options:

FlagDescription
--tierNew compute tier (e.g. a, b, c, d). Keeps the current tier when omitted.
--replicasNew replica count (must be >= 1). Keeps the current count when omitted.
--siteSite name or slug to scale. Defaults to the project’s active or only site.

Output:

📌 my-app (slug: main) Current: tier b · 2 replica(s) c × 3 = 36 pts · 14 remaining after ✅ 'my-app' scaled to tier c · 3 replica(s) The app is moving to the new size now, with no downtime.

The current size is shown first, then the new points footprint (tier × replicas) is previewed before the change is sent. The · N remaining after tail accounts for the difference a resize spends, and appears only when your project has a points budget.

The running app changes right away. A new tier rolls the app onto the new size: a pod with the new size starts and takes traffic before an old one stops, so the app stays up. A replica change alone adds or removes pods without restarting the others. The last line says whether the running app is moving now; when it is not, it says when the new size takes effect instead:

The site has not been deployed yet; its first deploy uses the new size.

A smaller tier must fit the app’s memory. Moving to a tier with less memory is refused when the app used more than 90 % of that tier’s memory at any point in the last 7 days, because the smaller app would run out of memory and be restarted. Nothing changes, and the message names the smallest tier that fits:

❌ Failed to scale: the app used up to 615 MB of memory in the last 7 days, which does not fit tier a with room to spare; the smallest tier it fits is b

CPU is not checked: a smaller CPU limit slows the app down, it does not stop it. An app with no memory use recorded yet (never deployed, or not running) can move to any tier.

Tier prices and points come from the live catalog — the figures above are only an example.

Notes:

  • Replicas must be >= 1. Pause / scale-to-zero isn’t supported yet, so --replicas 0 is rejected before any request: ❌ replicas must be >= 1 (pause / scale-to-zero is not yet supported).
  • An app that has never been sized has no compute tier yet — set one by passing --tier: ❌ this app has no compute tier yet — specify --tier <tier> to set one.
  • Passing neither flag does nothing: Nothing to change. Pass --tier and/or --replicas.
  • CLI versions before v0.11.10 print only the ✅ line. The running app still changes right away; only the message is missing.
  • Check what any app reserves with ghayma points; for the pricing model see Points & Plans.

Renaming a site

The CLI has no site rename command yet. Rename from the Dashboard  or with Update Site in the API.

Renaming a site is two very different operations depending on whether the new name produces the same slug:

Cosmetic rename. "api" → "API" both slugify to api. Only the display name changes, nothing moves, and it takes effect immediately.

URL-moving rename. "api" → "public-api" derives a new slug, and the slug is the site’s identity: its URL, its Kubernetes resources, its container image repository and its network policies all carry it. So this one moves the site’s URL, has to be acknowledged explicitly, and runs as a background migration — your app keeps serving on the old URL until the cutover, then serves on the new one.

Renaming to or from main works the same way. A site renamed to main takes over the project’s bare <project>.ghayma.app URL; a main site renamed to anything else gives it up.

While a rename is running, deploys, rollbacks and deletes on that site are rejected — and a rename is rejected while any of those are in flight.

The old URL keeps working for 30 days

After a URL-moving rename, the site’s old .ghayma.app URL is not deleted. It becomes a temporary redirect to the new URL for 30 days, so links, bookmarks, webhook registrations and CI scripts you already shipped keep landing on your app while you migrate them. After 30 days a daily sweep removes it and the old URL stops resolving.

  • The redirect is temporary on purpose (302 for browsers, 307 for other methods, so a POST to an old webhook URL still arrives as a POST). A permanent redirect would be cached by browsers long after the window closed.
  • The path is preserved: old.ghayma.app/webhooks/stripe → new.ghayma.app/webhooks/stripe.
  • Each site keeps at most one grace redirect, so repeated renames never chain. Renaming back — or re-creating a site under the old name — reclaims the old URL immediately.
  • Custom domains are never touched by a rename. They stay attached to the site and keep serving throughout.

The same 30-day grace applies when you rename the project: every one of its sites keeps its old URL as a redirect. See Rename Project.

Deleting a site

The CLI has no site delete command yet. Delete from the Dashboard  or with Delete Site in the API.

Any site can be deleted, including main and including the default site. Deleting the last site leaves the project site-less — exactly how it started — and the next deploy re-creates main.

Deleting a site removes, in one step:

  • the running app, its deployment history and its environment variables
  • every domain attached to it, including its default .ghayma.app URL
  • its cron jobs and their run history
  • its connections to the project’s databases, buckets and auth apps

Purchased-domain records survive — you paid for those — they are just unlinked from the deleted site and can be re-attached elsewhere.

Deleting the main or the default site needs explicit confirmation — these are the two deletes that used to be refused outright. Deleting main takes down the project’s bare <project>.ghayma.app URL; deleting the default site leaves the project with no default until you set another. The Dashboard asks and lists the consequences; the API requires {"confirm": true} in the request body. Any other site deletes without ceremony.

Deletes are refused while a deployment is building on that site, or while the site or its project is being renamed. Wait for the operation to finish and retry.

Because points are charged for what you actually run, dropping a site releases its app and cron-job points immediately — there is no separate refund step.

Multi-site workflow

Monorepo example

Recommended — one manifest at the workspace root:

my-project/ ├── .ghayma.json ← manifest: frontend → apps/frontend, admin → apps/admin ├── apps/ │ ├── frontend/ │ └── admin/ └── packages/ └── shared/
cd my-project ghayma link my-project # choose "Whole project" — maps every site to a directory ghayma deploy --site frontend --prod ghayma deploy --site admin --prod

One link covers every app in the workspace, and deploy from the root asks which site you meant when you don’t pass --site. See Workspaces (monorepos).

Alternative — a config file inside each app:

my-project/ ├── apps/ │ ├── frontend/ ← .ghayma.json (site: frontend) │ └── admin/ ← .ghayma.json (site: admin) └── packages/ └── shared/

Two equivalent setup paths:

Option A — init from inside each app directory:

cd apps/frontend ghayma init # name: my-project, site: frontend ghayma deploy --prod cd ../admin ghayma init # same project_id, site: admin ghayma deploy --prod

Option B — init from the monorepo root:

If you run ghayma init from the monorepo root (where turbo.json, or a pnpm-workspace.yaml with a packages: list, lives), the CLI detects the workspace and asks for an app subdirectory first. It then writes .ghayma.json inside that subdir with root_directory set, so deploys pick up the whole workspace but build the right target. See Monorepo initialisation.

Per-app files keep working after you link the workspace, and link copies their build settings into the manifest — so you can move to a manifest without touching a live build.

Each site gets its own Kubernetes deployment, its own URL, and its own env vars.

Picking a site per command

With a manifest at the workspace root, there is nothing to switch — say which site you mean, or let the directory say it for you:

# Name the site ghayma deploy --site admin --prod ghayma env list --site admin # Or run from the app's own directory — the site is implied cd apps/admin && ghayma deploy --prod

With a config file inside each app, switch the active site instead:

# Check which site is active cat .ghayma.json | grep site_name # Switch to a different site ghayma site use admin # Deploy to admin site ghayma deploy --prod # Switch back ghayma site use frontend

Each site has its own

  • Environment kind — production, staging or development, which is what makes a deploy a production one. The default site is always production — see Environments
  • Deployments — independent build + deploy history
  • URL — {project-slug}-{site-slug}.ghayma.app (except main, which uses {project-slug}.ghayma.app) — see Site URLs
  • Domains — custom domains are attached per-site
  • Environment variables — ghayma env set applies to one site: the one you’re in, or the one you name with --site
  • Build settings — framework, install/build/start commands, output directory, port, Dockerfile path, and whether to always generate the Dockerfile. Edit them on the site’s Settings page, or declare them per site in .ghayma.json — see Where build settings live
  • Cron jobs — attached to a site and removed with it

Sites share

  • Databases — owned by the project, not by a site; each app reaches one through a connection, set with ghayma db sites or in the dashboard, and a connected site also receives that database’s connection variables
  • Storage buckets — owned by the project; an app receives a bucket’s credentials when it is connected to it
  • Auth apps — owned by the project; an app receives an auth app’s variables when it is connected to it
  • Teams & billing — project-level
  • Bandwidth & usage — metered per project: every site’s bandwidth and compute usage is summed into the project’s usage, and draws down the one plan bandwidth allowance — see Points & Plans
  • The custom-Dockerfile permission — the Use custom Dockerfile switch in project Settings → Advanced applies to every site — see Custom Dockerfile

Dashboard

The dashboard at dash.ghayma.cloud  is where site renames and deletes live today:

  1. Open your project
  2. Click Sites in the left sidebar
  3. Create, view, rename and delete sites — including main
  4. Set which site is the project’s default (the one implicit calls resolve to)
  5. Click Env Vars or Domains next to any site to manage them directly
  6. Open a site and click Settings for its build settings — framework, install/build/start commands, output directory, port, Dockerfile path, and Always generate the Dockerfile for this site. The project’s Settings tab points you here; editing needs the admin or owner role, everyone else sees them read-only

A rename that changes the site’s URL shows you the old and new URL and asks you to agree before it starts, then reports progress while the migration runs. Deleting the main or default site asks for confirmation and lists exactly what stops working.