CI reads Infisical staging as the read-only convexmonorepo-ci machine identity, deploys the Convex functions, builds and pushes the image, and asks the VPS to redeploy. Payload schema changes now ship as migrations that production runs on startup; the baseline matches the production schema and is recorded as applied there. The local stack moves to its own ports and syncs the Convex runtime env, and the scripts pick up Infisical account switching, dev-kill and a cache-safe CI install.
Convex Turbo Monorepo
A reusable Bun/Turborepo template with Next.js 16, Expo, self-hosted Convex, Payload CMS, shared UI/config packages, Vitest, and Docker deployment.
Local setup
Requirements: Bun 1.3.14, Docker or Podman, Node 22, the Infisical CLI, jq, and
flock.
bun install --frozen-lockfile
infisical login
infisical init
bun db:up
bun dev:next
The committed .infisical.json links this repository to its own Infisical
project. Local commands read dev by default and never fall back to .env
files. Select staging with INFISICAL_ENV=staging bun dev:next.
Local services:
- Next.js:
http://localhost:3000 - Convex API:
http://localhost:3240 - Convex dashboard:
http://localhost:6794 - Payload Postgres:
localhost:5436
Next and Expo run on the host. Payload uses the local Postgres database. Convex
uses a separate self-hosted data volume and does not use Postgres by default.
The commented POSTGRES_URL in Compose is an opt-in example for cloned projects.
bun db:down # stop; preserve Payload and Convex data
bun db:down:wipe # remove both volumes, generated admin key, and seed marker
bun db:sync:payload # refresh/apply the local Payload seed snapshot from staging
db:sync:payload reads the staging PAYLOAD_DB_URL, saves a private PostgreSQL
snapshot under .local/, and replaces only the localhost Payload database. It
never writes to staging or Convex. Normal bun db:up never contacts staging;
it only restores .local/payload-staging.dump when the local database marker is
missing or stale. The marker is .local/payload-seed-state.env, not a database
table, so Payload schema push will not try to drop it. Stop the Next development
server first so it does not reconnect while the database is being replaced. The
snapshot includes Payload users and password hashes, allowing the same admin
credentials locally; protect it as production-derived data and delete it when no
longer needed.
Physical devices cannot resolve their own localhost; override the public
Convex URL with the development host's LAN address when testing Expo on-device.
Environment model
- Local
devandstaging: Infisical. - Generated local state:
.local/<environment>.generated.env. - CI/CD: Infisical
staging, read by the read-onlyconvexmonorepo-cimachine identity and materialized only as a temporary runner file. - Docker compilation: explicit Compose build args;
.env*stays outside the image context.
Run sh scripts/with-env dev -- <command> for an environment-aware command or
sh scripts/export-env dev to materialize a temporary merged dotenv stream.
Do not commit or maintain root .env files.
Development and quality
bun dev:next
bun dev:expo
bun lint:ws
bun format
bun lint
bun typecheck
bun test:unit
bun test:integration
bun test:component
SKIP_E2E=1 bun run ci:check
bun test:e2e starts the isolated local stack and currently performs generic
stack smoke checks. It skips in CI and when SKIP_E2E=1 is set.
Shared dependency versions belong in root catalogs. Edit the root catalog, run
bun install, then bun lint:ws; do not run bun update inside a workspace.
Deployment
Production Compose retains the self-hosted Convex backend/dashboard and accepts
an external Payload PAYLOAD_DB_URL. Its commented Postgres service remains an
optional Payload database.
A push to main runs the quality gate, deploys the Convex functions, builds the
Next image, pushes SHA and latest tags, and asks the VPS to redeploy
podman-convexmonorepo.service over the forced-command deploy key. If that last
step fails, the nightly update timer deploys the image instead. Each job reads
Infisical staging through scripts/ci-export-env.mjs as the convexmonorepo-ci
machine identity (Gitea secrets INFISICAL_CLIENT_ID and
INFISICAL_CLIENT_SECRET).
Payload schema changes ship as migrations; see AGENTS.md.