Projects and environments

Two organizing concepts above the App level:

  • Project — a collection of related apps owned by an Organization. Example: org acme has project storefront containing apps web, api, worker.
  • Environment — a deployment slot inside a project. Each project has at least one environment (production, auto-created); add more for staging, preview, etc.

Apps belong to a single Project. The same App deploys into one or more of the project's Environments. Each (App, Environment) pair has its own env vars, domains, managed databases, scale settings, deployments, and live containers.

Hierarchy

Organization (acme)
└── Project (storefront)
    ├── Environment (production)   ← auto-created, BranchPattern "main"
    ├── Environment (staging)      ← optional, BranchPattern "staging"
    └── App (web)
        ├── per-env: env vars, domains, databases, scale, deployments
        └── one git remote (shared across envs)

Branch-based deploys

When you push to a branch, the API queues a deploy in every Environment whose BranchPattern matches:

Push to With these envs Triggers deploys to
main production (main) production
staging production (main), staging (staging) staging
feature/xyz production (main), staging (staging), preview (*) preview
feature/xyz production (main) (skipped)

A specific match (main) wins over the catch-all *. If no env matches, the push is accepted but no deploy is queued — the API returns { skipped: true, reason: "..." } and the git client sees [paas] skipped: ....

Auto-assigned hostnames

Every (App, Environment) gets an apex hostname:

{org-slug}.{project-slug}-{app-slug}-{env-slug}.{wildcard-base}

For org acme, project storefront, app web, env production, with PAAS_WILDCARD_BASE=apps.example.com:

acme.storefront-web-production.apps.example.com
acme.storefront-web-staging.apps.example.com

The apex is HTTP-only by default. Let's Encrypt's HTTP-01 challenge can't issue wildcard certs for *.apps.example.com, so the apex stays plain HTTP. Custom domains do get TLS — see below.

What's per-environment vs shared

Aspect Scope Notes
Code / Dockerfile App One git remote, shared across envs.
Container port App Same port everywhere.
Env vars (App, Env) Set DATABASE_URL, API_KEY, LOG_LEVEL differently per env.
Domains (App, Env) Production at your-domain.com, staging at staging.your-domain.com.
Managed Postgres (App, Env) Different DB instance per env — staging cannot see prod data.
Scale (replicas, CPU, memory) (App, Env) Run prod with 3 replicas / 1GB; staging with 1 replica / 256MB.
Deployments (App, Env) Each env has its own deployment history.

CRUD via API

# List projects in an org
curl -H "Authorization: Bearer $TOKEN" \
  https://paas.example.com/api/orgs/acme/projects/

# Create a project (Production env auto-created)
curl -X POST -H "Authorization: Bearer $TOKEN" -H "Content-Type: application/json" \
  -d '{"slug":"storefront","displayName":"Storefront"}' \
  https://paas.example.com/api/orgs/acme/projects/

# Add a staging env
curl -X POST -H "Authorization: Bearer $TOKEN" -H "Content-Type: application/json" \
  -d '{"slug":"staging","displayName":"Staging","branchPattern":"staging","makeDefault":false}' \
  https://paas.example.com/api/orgs/acme/projects/storefront/envs/

# Create an app — bound to all current envs automatically
curl -X POST -H "Authorization: Bearer $TOKEN" -H "Content-Type: application/json" \
  -d '{"slug":"web","displayName":"Web","containerPort":8080}' \
  https://paas.example.com/api/orgs/acme/projects/storefront/apps/

# Set an env var, only on staging
curl -X PUT -H "Authorization: Bearer $TOKEN" -H "Content-Type: application/json" \
  -d '{"value":"true"}' \
  https://paas.example.com/api/orgs/acme/projects/storefront/apps/web/envs/staging/env-vars/DATABASE_DEBUG

# Scale just production
curl -X PATCH -H "Authorization: Bearer $TOKEN" -H "Content-Type: application/json" \
  -d '{"replicas":3,"cpuMillis":1000,"memoryMb":512}' \
  https://paas.example.com/api/orgs/acme/projects/storefront/apps/web/envs/production/scale

CRUD via dashboard

/orgs → click an org → /projects?org=… → click a project → /apps?org=…&project=… → add envs / create apps. App detail page (/app?org=…&project=…&slug=…) has an env tab bar — switching tabs shows that environment's deployments, env-vars, domains, databases, and live logs.

Deleting

  • Cannot delete the default environment of a project — change which env is default first (PATCH /envs/{env} with makeDefault:true on another).
  • Deleting an environment removes its env-vars, domains, databases, and containers. The app's git repo and other envs are unaffected.
  • Deleting a project soft-deletes it (it stops accepting pushes / new deploys but is recoverable until you hard-purge from the DB).