Monorepos

Paas supports both shapes:

  • One repo per app — the default. git push to that repo deploys that one app.
  • Monorepo — one git repo holds many apps. A push fans out to only the apps whose code (or watched paths) actually changed.

The model is the same in both cases: each App points to a Repository and declares a BuildContext (a subdirectory) plus optional WatchPaths (glob patterns). The default for a single-app repo is BuildContext='.' — the whole repo is the build context.

Setting up a monorepo

1. Create the Repository

Dashboard → project → Repositories card → Create repo. Slug = mono (or whatever you like). The dashboard returns a clone URL:

git@git.example.com:acme/storefront/mono.git

2. Create one App per service

For each service in the monorepo, create an App and select Use existing repo: mono. Set its Build context to the subdirectory that holds the service code (relative to repo root). Optionally add Watch paths for shared code:

App Build context Watch paths
frontend apps/web packages/ui/**
backend services/api packages/db/**
worker workers/jobs packages/db/**

3. Push once

git remote add paas ssh://git@git.example.com:2222/acme/storefront/mono.git
git push paas main

The git server's post-receive hook computes git diff --name-only OLD..NEW and sends the file list to the API. The API:

  1. Picks Apps in this repo whose BuildContext (or WatchPaths) match any of the changed files.
  2. For each matching App, picks Environments whose BranchPattern matches the pushed branch.
  3. Queues a deployment per (App × Env) pair.

The git client sees one line per queued deploy:

[paas] push branch=main sha=a1b2c3d files=12
[paas] queued: app=frontend env=production deployment=5869…
[paas] queued: app=worker  env=production deployment=fc81…

If nothing matches, you get a friendly skip:

[paas] skipped: no app's BuildContext/WatchPaths matches the 3 changed file(s)

Path matching rules

For each App in the pushed repo:

  1. If BuildContext == "."always redeploy (whole-repo project).
  2. Else if any changed file starts with BuildContext + "/" → redeploy.
  3. Else if any changed file matches one of WatchPaths (glob) → redeploy.
  4. Else skip.

Glob syntax

Pattern Matches
* any chars except /
** any chars including / (any depth)
? a single non-slash char
packages/ui/** every file under packages/ui/
*.proto any .proto at the repo root
**/migrations/** any path with a migrations/ segment

Anchored to the repo root. Patterns are normalized to forward slashes.

How the build differs

Builder runs:

docker build -t <registry>/<org>/<project>/<app>:<sha> \
             -f <BuildContext>/<DockerfilePath> \
             <BuildContext>

So the docker context only contains the App's subdirectory. If a service needs files from outside its BuildContext at build time (e.g. a shared proto file in packages/proto/), use a multi-stage build that copies them explicitly via a build context that includes them — typically by setting BuildContext='.' and selecting the Dockerfile via DockerfilePath:

BuildContext  = .
DockerfilePath = apps/web/Dockerfile
WatchPaths    = ["apps/web/**", "packages/**"]

This is the most flexible setup for monorepos that share code; the trade-off is the docker build sees the whole repo as context, which is bigger and slower.

Editing branches per environment

Each environment has a BranchPattern that decides what triggers a deploy. You can change it for any environment, including the auto-created Production. Dashboard → project → Environments card → click Edit next to the pattern.

Common setups:

Project shape Production pattern Staging pattern Preview pattern
Trunk-based main (not used) (not used)
GitFlow release develop *
GitHub Flow + previews main staging *

Specific patterns (main, staging) win over the catch-all *. If you push to a branch matching only *, only environments using * deploy. If no env matches at all, the push is accepted but no deploys are queued — the git client sees [paas] skipped: no environment matches branch '...'.

CLI cheat-sheet

# Create a repo at the project level
curl -X POST -H "Authorization: Bearer $TOKEN" -H "Content-Type: application/json" \
     -d '{"slug":"mono"}' \
     https://paas.example.com/api/orgs/acme/projects/storefront/repos/

# Create an app pointing at a subdir of an existing repo
curl -X POST -H "Authorization: Bearer $TOKEN" -H "Content-Type: application/json" \
  -d '{
    "slug":"frontend",
    "displayName":"Frontend",
    "containerPort":3000,
    "repoSlug":"mono",
    "buildContext":"apps/web",
    "dockerfilePath":"Dockerfile",
    "watchPaths":["packages/ui/**"]
  }' \
  https://paas.example.com/api/orgs/acme/projects/storefront/apps/

# Update an existing app's build paths
curl -X PATCH -H "Authorization: Bearer $TOKEN" -H "Content-Type: application/json" \
  -d '{"buildContext":"apps/web","watchPaths":["packages/ui/**","packages/db/**"]}' \
  https://paas.example.com/api/orgs/acme/projects/storefront/apps/frontend

# Change Production's branch from `main` to `release`
curl -X PATCH -H "Authorization: Bearer $TOKEN" -H "Content-Type: application/json" \
  -d '{"branchPattern":"release"}' \
  https://paas.example.com/api/orgs/acme/projects/storefront/envs/production