Monorepos
Deploy multiple Ploy projects from one repository and share local emulator state across them.
Monorepos
Ploy detects monorepos automatically — there is no monorepo: true flag. A repository is treated as a monorepo when it contains workspace indicators (pnpm-workspace.yaml, package.json with workspaces, turbo.json, etc.) or when more than one ploy.yaml is present.
Each ploy.yaml becomes its own deployable project. You can keep an apps/api, an apps/web, and a packages/worker-jobs in the same repo, deploy them as three independent Ploy projects, and share resources (databases, queues, workflows) between them by pointing them at the same resource name.
Layout
my-app/
├── ploy-workspace.yaml # optional — workspace-level config
├── apps/
│ ├── api/
│ │ └── ploy.yaml # → project "api"
│ └── web/
│ └── ploy.yaml # → project "web"
├── packages/
│ └── worker-jobs/
│ └── ploy.yaml # → project "worker-jobs"
├── package.json
└── pnpm-workspace.yamlEach ploy.yaml is a complete, self-contained config. There is no merge with a parent file. Project names default to the parent directory; override with the name: field if you need something different.
kind: worker
name: api # optional — defaults to "api"
build: pnpm build --filter api
db:
DB: default # shares the "default" DB with any other project that names it
queue:
TASKS: tasksSharing resources between projects
Resources are addressed by their resource name (the lowercase string on the right side of the binding map). Two projects that bind to the same resource name read and write the same underlying resource — locally and in production.
db:
DB: defaultdb:
DB: default # same DB as apps/apiTo keep separate resources, use distinct names:
# apps/api/ploy.yaml
db:
DB: api_db
# apps/web/ploy.yaml
db:
DB: web_dbOnly one project may own migrations for a shared resource. If two projects
both ship migrations/DB/*.sql for the same resource name, Ploy errors out
with both project names so you can pick a single owner.
ploy-workspace.yaml
A workspace config at the repo root is optional. Add one when you need to:
- Exclude
ploy.yamlfiles that aren't deployable (e.g., examples, fixtures, templates) - Set workspace-level dev port ranges
- Pin the local dashboard port
exclude:
- examples/**
- templates/**
ports:
worker:
from: 8800 # auto-allocate worker ports starting here
dashboard:
port: 9787node_modules/ is always excluded by default.
Local development
Run ploy dev from the repo root to bring up all projects together:
$ ploy dev
Ploy workspace dev
Dashboard: http://localhost:9787
api worker http://localhost:8787
web worker http://localhost:8788What this gives you:
- One shared
.ploy/directory at the repo root — all SQLite DBs, file storage, and emulator state live here. Resources with the same name across projects share the same files, exactly as in production. - One shared dashboard at
http://localhost:9787exposing every project's bindings and the resources they touch. - One workerd per project on auto-allocated ports starting at
8787. Override per-project withdev: { port: 8800 }in that project'sploy.yaml. - Single
Ctrl-Cstops everything cleanly.
To run a subset:
$ ploy dev --project=api
$ ploy dev --project=api,webRunning ploy dev from inside a single project's directory still works the old way: that project alone is started with its own .ploy/ and dashboard. This preserves backward compatibility for repos that aren't yet set up as workspaces.
Cloud deployments
When you connect a GitHub repository, Ploy scans it for ploy.yaml files (using the GitHub API — no clone needed) and shows the detected list in the dashboard. You select which entries to enable as projects. Each row defaults its name to the parent directory; override inline.
After the initial connect, pushes to the default branch are diffed against the enabled set:
- A
ploy.yamlthat already maps to a project triggers a build for just that project. - A new
ploy.yamlthat wasn't previously enabled appears as a "new project detected" entry. Nothing deploys until you explicitly enable it. - A
ploy.yamlthat's removed from the repo leaves the corresponding project untouched (archive or delete it manually if no longer needed).
This means adding a deploy target to an existing monorepo is just git add apps/billing/ploy.yaml && git push — Ploy will surface the new project for confirmation rather than auto-deploying.
How base interacts with monorepos
In a monorepo, the build runs from the project's directory (the directory containing its ploy.yaml). Dependencies are installed from the repo root so workspace packages resolve. You don't need to set base: — it's inferred from the ploy.yaml location.
The base: field is only required if your ploy.yaml lives at the repo root but the deployable code lives in a subdirectory. In that case base: tells the builder where to run the build:
kind: nextjs
base: apps/web
build: pnpm build --filter webFor per-project ploy.yaml files in subdirectories, omit base: — it's inferred.
Migration from monorepo: true
The monorepo: true flag has been removed from ploy.yaml. Remove it from your config files; Ploy now detects monorepos automatically from workspace indicators in your repo.
If you previously had:
kind: nextjs
base: apps/web
monorepo: true
build: pnpm build --filter webUpdate to (drop both monorepo and base — the latter is inferred from the file's location):
kind: nextjs
build: pnpm build --filter webA validation error will point out any remaining monorepo: keys when you next run ploy build or push to a connected repo.
How is this guide?
Last updated on