Deployment
Deploy the UI, API worker, docs, cron schedule, and PostgreSQL database on Ploy.
Work on Repeat is prepared as four workspace apps:
apps/ui— one Next.js service for marketing, the blog, authentication, onboarding, and the dashboard;apps/api— Hono API, run execution, and scheduler;apps/db— Drizzle schema, migrations, and repositories; andapps/docs— this Fumadocs Next.js service.
Three of them are deployable and each carries its own ploy.yaml. Ploy detects
the monorepo automatically, installs from the repository root so workspace
dependencies resolve, and runs each build from its base directory.
Projects
| App | kind | Entry | Notes |
|---|---|---|---|
apps/ui | nextjs | App Router | Marketing, blog, auth, and dashboard |
apps/docs | nextjs | App Router | This documentation site |
apps/api | worker | src/worker.ts | Runs on workerd, with a cron trigger |
apps/api/src/worker.ts is the workerd entrypoint. src/server.ts remains the
Node entrypoint used by pnpm dev and by any non-Ploy host. The difference is
how background work happens: the Node process runs a polling run-worker, while
on Ploy the cron trigger drives the same operations tick and a manually
dispatched run executes on the request's waitUntil.
Ploy builds workers with nodejs_compat_v2, which is what lets Postgres.js open
a TCP connection to your database from the worker.
Environment contract
Set these on the API project:
API_ORIGIN
UI_ORIGIN
DATABASE_URL
NODE_ENV=production
DEMO_MODE=false
BETTER_AUTH_SECRET
ENCRYPTION_KEY
SCHEDULER_SECRET
LLM_GATEWAY_API_KEY
LLM_GATEWAY_DEFAULT_MODELSet API_ORIGIN and DOCS_ORIGIN on the UI project.
ENCRYPTION_KEY must be a base64-encoded 32-byte value, and
BETTER_AUTH_SECRET and SCHEDULER_SECRET must each be at least 32
characters. Secrets should differ in every environment. The API refuses to start
in production when fixture mode is enabled or LLM_GATEWAY_API_KEY is absent.
Every variable, including the optional OAuth providers, is documented in Environment variables.
Database
Ploy does not host PostgreSQL, so point DATABASE_URL at your own instance and
run pnpm db:migrate as a release step before the new version takes traffic.
Migrations are ordered and idempotent; the organizations-and-projects migration
preserves existing rows rather than recreating tables.
Scheduler
The apps/api project declares a one-minute cron trigger:
cron:
OPERATIONS_TICK: "* * * * *"Each tick claims due routines, executes queued runs, and retries webhook deliveries. Claims are atomic, so overlapping ticks never duplicate a run.
POST /internal/scheduler/tick with X-Scheduler-Secret remains available for
hosts that drive the schedule externally. On a deployment without a scheduler
configured it returns 503 scheduler_unavailable.
Without a tick, nothing runs
The scheduler is the only thing that claims due routines. If activated routines never execute, confirm the cron trigger is firing before looking anywhere else.
Before production traffic
nextRunAt.Confirm automatic execution for a test MCP tool and inspect its trace.