Bondery Docs

Dokploy deployment (Bondery production)

Canonical production topology — two Compose apps:

Bondery production only — not part of the self-host distribution. Self-hosters follow Installation.

HostServiceStack
app.usebondery.comwebapp :26632deploy/bondery Compose
api.usebondery.comapi :26631same Compose file
(internal)redis, dbsame Compose — never attach a public domain
usebondery.comwebsite :26630deploy/ops Compose (Bondery prod only)

Self-hosters use only deploy/bondery (api + webapp + redis + Postgres). Marketing lives in ops and is not part of the self-host distribution. Guides: deploy/bondery/README.md · docs/deploy/installation.mdx · deploy/ops/README.md.

Product Compose application (deploy/bondery)

SettingValue
ProviderDocker Compose
Compose pathdeploy/bondery/docker-compose.yml (includes docker-compose.postgres.yml)
DomainsTraefik labels via BONDERY_INFRA_WEBAPP_DOMAIN / BONDERY_INFRA_API_DOMAIN

Environment

Copy deploy/bondery/.env.example into Dokploy env (or a compose .env):

BONDERY_INFRA_API_DOMAIN=api.usebondery.com
BONDERY_INFRA_WEBAPP_DOMAIN=app.usebondery.com
BONDERY_INFRA_WEBSITE_DOMAIN=usebondery.com
BONDERY_INFRA_CHROME_EXTENSION_ID=lpcmokfekjjejnpobhbkgmjkodfhpmha
BONDERY_PRIVATE_REDIS_URL=redis://redis:6379

Plus Postgres password, Better Auth secrets, and OAuth vars from .env.example. The api service runs pre_start init containers before the main process on every deploy: release-migrate (schema + functions + OAuth + platform admins) then ensure-storage-buckets — no separate migrate service or manual first-boot step.

Important:

  • Image tags: production (floating channel) is the default; pin semver when you want a fixed rollback target.
  • Set hostnames only (BONDERY_INFRA_*_DOMAIN). Compose derives https://… URLs and Traefik Host() rules.
  • BONDERY_PRIVATE_BETTER_AUTH_SECRETS must use format version:secret (e.g. 1:<≥32-char secret>) in .env.
  • Webapp never receives raw BONDERY_PRIVATE_* secrets except its own session/OAuth client secrets — compose allowlists public vars for the webapp service.
  • Compose publishes unique Docker DNS aliases from BONDERY_INFRA_TRAEFIK_PREFIX (bondery vs bondery-beta) so production and beta can share dokploy-network. Lookups: webapp SSR → ${prefix}-api:26631; API S3 → ${prefix}-storage:8333. Do not use the Compose service names api or seaweedfs-s3 on that network.

Health checks

ServiceLivenessReadinessNotes
webapp containerGET /health/liveGET /health/readyWebapp process + runtime config only
webapp BFF → APIGET /api/health/liveGET /api/health/readyProxies upstream Fastify health
apiGET /health/liveGET /health/readyProcess up / Postgres, required S3 bucket HeadBucket, Redis, SMTP (critical — 503 when unhealthy); optional integrations degrade only
api bootonReady eager verifySame live checks as /health/ready for Postgres, storage, Redis, SMTP before accepting traffic (skipped in NODE_ENV=test)
api bootstrapapi pre_start init containersBefore main API starts: release-migrate (schema + functions + OAuth + platform admins) then ensure-storage-buckets
dbCompose healthcheckapi waits for service_healthy

Redeploy the full Compose app (Dokploy's default) so api pre_start runs when the API image or init definition changes — don't redeploy webapp alone on schema-changing releases.

CI redeploy webhook

After unified vX.Y.Z release tags, GitHub Actions fetches production Infisical and POSTs to BONDERY_OPS_DOKPLOY_SERVICES_DEPLOY_WEBHOOK with refs/heads/release. Set the Dokploy Compose app branch to release. Leave the webhook unset in Infisical to redeploy manually.

Infisical → Dokploy env sync

Product stack secrets and hostnames can be synced via .github/workflows/sync-dokploy-env.yml:

  • Production: deployment: production, target: services — Infisical production; requires BONDERY_OPS_DOKPLOY_SERVICES_COMPOSE_ID (and optional BONDERY_OPS_DOKPLOY_SERVICES_DEPLOY_WEBHOOK).
  • Beta: deployment: beta, target: services — Infisical staging app secrets + production ops keys; requires BONDERY_OPS_DOKPLOY_STAGING_SERVICES_COMPOSE_ID and optional BONDERY_OPS_DOKPLOY_STAGING_SERVICES_DEPLOY_WEBHOOK in Infisical production.

See workflows README.

Not synced (keep in Dokploy UI or compose derives): BONDERY_INFRA_GIT_SHA, BONDERY_INFRA_VERSION, BONDERY_PRIVATE_S3_ENDPOINT, BONDERY_PUBLIC_STORAGE_URL. BONDERY_INFRA_TRAEFIK_PREFIX syncs from Infisical (bondery in production, bondery-beta in staging).

Isolated Deployments

Leave Dokploy Isolated Deployments off. Production and beta share dokploy-network; unique ${BONDERY_INFRA_TRAEFIK_PREFIX}-* aliases keep Docker DNS from colliding. Optional Isolated Deployments are for operators running unrelated Compose apps, not for Bondery beta vs production.

Ops Compose application (deploy/ops — marketing website)

SettingValue
ProviderDocker Compose
Compose pathdeploy/ops/docker-compose.yml
Imageghcr.io/usebondery/website:${BONDERY_INFRA_WEBSITE_IMAGE_TAG:-production} (floating :production when unset)
DomainBONDERY_INFRA_WEBSITE_DOMAIN Traefik label → port 26630

CI: Path-filtered PRs and pushes to main run website-build in .github/workflows/verify.yml (pruned production next build). Push to release runs .github/workflows/deploy-website.yml (Docker build-push → smoke → optional Dokploy webhook from Infisical production); image tags :production + :sha-<short>. Infisical key BONDERY_OPS_DOKPLOY_WEBSITE_DEPLOY_WEBHOOK triggers redeploy (refs/heads/release).

BONDERY_INFRA_WEBAPP_DOMAIN=app.usebondery.com
BONDERY_INFRA_WEBSITE_DOMAIN=usebondery.com

Compose derives BONDERY_PUBLIC_*_URL from those domains. Health: GET /health/live (liveness), GET /health/ready (env valid).

Cutover from Nixpacks: stop the old Nixpacks/Railpack website app before deploying ops Compose (same Traefik Host). Details: deploy/ops/README.md.

Webapp runtime config

The webapp exposes GET /runtime-config.json and injects window.__BONDERY_RUNTIME_CONFIG__ during SSR — build once, deploy many.

After changing domain or BONDERY_PUBLIC_* variables, redeploy the affected service(s) (no image rebuild for env-only changes).

Better Auth URLs

Social login and sessions are handled by the API (/auth/*) — the API is its own issuer/authorization server (baseURL = BONDERY_PUBLIC_API_URL). Webapp, mobile, and the chrome extension are each independent OAuth 2.1 + PKCE clients of that one authorization server.

SettingValue
API base (issuer)https://api.usebondery.com (BONDERY_INFRA_API_DOMAIN)
Webapp's own OAuth client redirecthttps://app.usebondery.com/auth/oauth-callback
Chrome extensionOAuth 2.1 via API; redirect https://{BONDERY_INFRA_CHROME_EXTENSION_ID}.chromiumapp.org/

GitHub / LinkedIn OAuth apps

Authorization callback URL must be the API's Better Auth callback (basePath is /auth):

https://api.usebondery.com/auth/callback/github
https://api.usebondery.com/auth/callback/linkedin

(Replace host with your BONDERY_INFRA_API_DOMAIN.)

Register the webapp and extension as trusted OAuth clients (once per environment)

The webapp is a confidential OAuth client, and the Chrome extension a public OAuth client, of the API's own authorization server. Both are provisioned deterministically from env vars — there is no signed-in-session registration step. Generate values yourself and set them before first deploy:

openssl rand -hex 16   # -> BONDERY_PUBLIC_WEBAPP_OAUTH_CLIENT_ID
openssl rand -hex 32   # -> BONDERY_PRIVATE_WEBAPP_OAUTH_CLIENT_SECRET
openssl rand -hex 16   # -> BONDERY_PUBLIC_OAUTH_CLIENT_ID (extension)

Set BONDERY_PUBLIC_WEBAPP_OAUTH_CLIENT_ID (api + webapp), BONDERY_PRIVATE_WEBAPP_OAUTH_CLIENT_SECRET (webapp), BONDERY_PUBLIC_OAUTH_CLIENT_ID (api + extension), plus a BONDERY_PRIVATE_WEBAPP_SESSION_SECRET (≥32 chars) for the webapp's own encrypted session cookie. OAuth clients are upserted automatically when api pre_start runs release-migrate (after prisma migrate deploy).

Environment migration (pre-1.7.x → BONDERY_*)

If the API container crashes with missing NEXT_PUBLIC_* or legacy deploy paths, you are on an old API image.

  1. Pin images to 1.7.4 or newer (or redeploy after :production has moved past the env migration).
  2. Rename Dokploy / compose .env keys to BONDERY_* — see deploy/bondery/.env.example.
  3. Redeploy the stack and pull fresh images (docker compose pull or Dokploy redeploy).

Cutover (API-only Compose + separate webapp image → unified stack)

Preflight

  1. Record current image digests / env for API Compose and standalone webapp.
  2. Keep the standalone webapp Dokploy app running as rollback until verified.
  3. Redis volume continuity is optional (rate-limit / sync-wake / WS tickets only — disposable).

Steps

  1. Point the existing Compose app at deploy/bondery/docker-compose.yml (in-place preferred).
  2. Set domains and secrets from .env.example (image tags optional — unset falls back to :production).
  3. Deploy; wait until redis and db healthy and api /status OK.
  4. Confirm Traefik routes: api.usebondery.comapi:26631, app.usebondery.comwebapp:26632. Do not route Redis or Postgres publicly.
  5. Stop the old standalone webapp app if it conflicts on the domain.
  6. Smoke: curl /health/live, /health/ready, /api/health/ready; login; one authenticated mutation; sync/WebSocket if used.

Rollback

  1. Re-point app.usebondery.com to the preserved standalone webapp image app.
  2. Restore previous image tags or digests and redeploy.
  3. A fresh Redis volume on rollback is acceptable.

After 24–48h healthy bake

  1. Remove the old Dokploy webapp Docker Image application.

API with external Redis

Advanced: run the API image alone with managed Redis — see api-container.md.

On this page