Bondery Docs

Environment configuration

Bondery uses one naming scheme everywhere and one root file for local development.

Naming contract

PrefixMeaningWho may read it
BONDERY_PUBLIC_*Safe for browsers / mobile / extensionAny product surface
BONDERY_PRIVATE_*SecretsAPI (and webapp session encryption keys)
BONDERY_INFRA_*Domains, image tags, internal DNS, build metadataDeploy + webapp runtime
BONDERY_DEV_*Local API dev boot toggles (pnpm run dev only)API — never set in Compose/production
BONDERY_OPS_*CI / release toolingGitHub 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_.

ContextWhat you set
Local devOnly BONDERY_PRIVATE_POSTGRES_PASSWORD in root .env.localpnpm run env derives DATABASE_URL into apps/api and packages/db
Self-host composeOnly BONDERY_PRIVATE_POSTGRES_PASSWORD — compose derives DATABASE_URL for the api container
WebappNothing — 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.

VariableWhen true
BONDERY_DEV_SKIP_RELEASE_MIGRATESkip migrate + functions.sql + OAuth + platform admin promotion on API dev boot
BONDERY_DEV_SKIP_STORAGE_BUCKETSSkip 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
ScriptPurpose
pnpm run setup:devFirst clone: create .env.local, then run env
pnpm run envSync root .env.local → development + production per-app locals → validate
pnpm run env:developmentSync only *.development.local (+ mobile/db .env.local)
pnpm run env:productionSync only *.env.production.local (for local pnpm run build)
pnpm run env:pullPull syncable manifest secrets from Infisical → .env.localenv:development
pnpm run env:syncBuild helpers, regenerate examples + turbo.json, run check:versions
pnpm run env:examplesRegenerate 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 files

Optional 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:stripe

Authoring vs generated files

EditDo not edit
Root .env.local (gitignored)apps/*/.env*.local (generated)
Manifest packages/helpers/src/env/manifest.tsGenerated *.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 use NEXT_PUBLIC_* for product config.
  • Website — Server Components / route handlers read BONDERY_PUBLIC_* from process.env. Client leaves receive values as RSC props (no next.config env, no NEXT_PUBLIC_*).
  • Mobile — Expo only auto-inlines EXPO_PUBLIC_*. We load BONDERY_PUBLIC_* in app.config.ts into extra, and apps/mobile/src/lib/config.ts reads Constants.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).

On this page