Environment configuration
Bondery uses one naming scheme everywhere and one root file for local development.
Naming contract
| Prefix | Meaning | Who may read it |
|---|---|---|
BONDERY_PUBLIC_* | Safe for browsers / mobile / extension | Any product surface |
BONDERY_PRIVATE_* | Secrets | API (and webapp session encryption keys) |
BONDERY_INFRA_* | Domains, image tags, internal DNS, build metadata | Deploy + webapp runtime |
BONDERY_DEV_* | Local API dev boot toggles (pnpm run dev only) | API — never set in Compose/production |
BONDERY_OPS_* | CI / release tooling | GitHub Actions only — not synced into app local files |
Same fact → same key name in every app (e.g. BONDERY_PUBLIC_API_URL on api, webapp, extension, and mobile).
DATABASE_URL
DATABASE_URL is the standard Postgres connection string for Prisma, Better Auth, and pg-boss. It is not prefixed with BONDERY_.
| Context | What you set |
|---|---|
| Local dev | Only BONDERY_PRIVATE_POSTGRES_PASSWORD in root .env.local — pnpm run env derives DATABASE_URL into apps/api and packages/db |
| Self-host compose | Only BONDERY_PRIVATE_POSTGRES_PASSWORD — compose derives DATABASE_URL for the api container |
| Webapp | Nothing — the webapp calls the API over HTTP |
Used in apps/api, packages/db (migrations), and packages/db/scripts/*.
BONDERY_DEV_* (API local boot)
Optional flags read only when NODE_ENV=development during pnpm run dev. Compose api pre_start ignores them — production always runs the full bootstrap CLIs.
| Variable | When true |
|---|---|
BONDERY_DEV_SKIP_RELEASE_MIGRATE | Skip migrate + functions.sql + OAuth + platform admin promotion on API dev boot |
BONDERY_DEV_SKIP_STORAGE_BUCKETS | Skip SeaweedFS bucket ensure on API dev boot |
Local happy path
pnpm install
pnpm run setup:dev
# edit .env.local (OAuth clients, BONDERY_PRIVATE_POSTGRES_PASSWORD, secrets)
cd deploy/bondery && docker compose -f docker-compose.dev-db.yml up -d
cd ../..
pnpm run db:migrate:deploy
pnpm run env
pnpm run provision:oauth-clients
pnpm run dev| Script | Purpose |
|---|---|
pnpm run setup:dev | First clone: create .env.local, then run env |
pnpm run env | Sync root .env.local → development + production per-app locals → validate |
pnpm run env:development | Sync only *.development.local (+ mobile/db .env.local) |
pnpm run env:production | Sync only *.env.production.local (for local pnpm run build) |
pnpm run env:pull | Pull syncable manifest secrets from Infisical → .env.local → env:development |
pnpm run env:sync | Build helpers, regenerate examples + turbo.json, run check:versions |
pnpm run env:examples | Regenerate only (no helpers build / version check) |
env:development and env:production use the same values from root .env.local (localhost URLs). Local production-mode builds are not deploys — they need your local URLs, not usebondery.com.
Pre-commit runs env:sync --stage when you change the manifest, env scripts, or root package.json — same pattern as OpenAPI.
pnpm run env:check # CI + `pnpm run check`: regenerate examples; fail if git dirty
pnpm run check:env # `pnpm run check` only: per-app required vars against local env filesOptional flags on env: --no-pull, --skip-check, --dry-run, --only=api,webapp.
Infisical (env:pull)
Optional for local dev when you need shared team secrets (SMTP, GitHub OAuth, Mapy, S3, etc.). Manifest entries with syncable: true define which canonical keys are pulled. See the comment block above ENV_MANIFEST in manifest.ts for sync recommendations.
# one-time
cp .infisical.json.example .infisical.json # set projectId, domain, defaultEnvironment
infisical login
pnpm run env:pull.infisical.json (gitignored — copy from example) links the repo to your Infisical project. It contains no secrets (only project id, EU/US domain, environment slug, and optional secretsPath). Use the environment slug from Infisical project settings: development (local env:pull), staging (release smoke), or production (Dokploy ops sync).
Stripe CLI (local webhooks)
env:pull syncs shared Stripe test keys and price IDs. It does not pull BONDERY_PRIVATE_STRIPE_WEBHOOK_SECRET — that value is a per-machine Stripe CLI listen secret locally, and a Dashboard endpoint secret in Infisical production/staging.
# one-time (Stripe CLI on PATH; does not run stripe login)
stripe login
pnpm run setup:stripe # or: pnpm run setup:stripe -- --force
# restart the API after the secret changes
# while the API is running
pnpm run dev:stripeAuthoring vs generated files
| Edit | Do not edit |
|---|---|
Root .env.local (gitignored) | apps/*/.env*.local (generated) |
Manifest packages/helpers/src/env/manifest.ts | Generated *.example files (headers say “do not edit”) |
Templates: .env.local.example (local dev) · deploy/bondery/.env.example (self-host compose) · deploy/ops/.env.example (ops marketing stack).
Platform adapters (why not NEXT_PUBLIC_ / EXPO_PUBLIC_)
- Webapp — runtime config via dynamic
process.env[name]+/runtime-config.json(same Docker image, many environments). Never useNEXT_PUBLIC_*for product config. - Website — Server Components / route handlers read
BONDERY_PUBLIC_*fromprocess.env. Client leaves receive values as RSC props (nonext.configenv, noNEXT_PUBLIC_*). - Mobile — Expo only auto-inlines
EXPO_PUBLIC_*. We loadBONDERY_PUBLIC_*inapp.config.tsintoextra, andapps/mobile/src/lib/config.tsreadsConstants.expoConfig.extra. - Chrome extension — Vite
envPrefix: ["BONDERY_PUBLIC_", "WXT_"].
Out-of-runtime Next scripts (check-env, announce) call loadEnvConfig from @next/env so load order matches next dev / next build.
Production / self-host
Operators copy deploy/bondery/.env.example to deploy/bondery/.env for Compose — same BONDERY_* names; Compose derives DATABASE_URL and public URLs from domains. The example file is generated from the manifest (deployExample metadata) via pnpm run env:sync. See Installation. Bondery production deployment on Dokploy is documented in Dokploy deployment (contributors / operators only).
Manifest
@bondery/helpers/env is the single contract: canonical keys, which apps receive them, requiredIn, deploy operator examples (deployExample / opsExample), and turbo scopes. Per-app check-env scripts read required lists from the manifest (API also cross-checks required-env.ts).