Multi-tenant mode
Set TENANT_MODE=multi to host many isolated vaults on one process. Each user gets {slug}.yourservice.com, their own rows in shared SQLite (scoped by tenant_id), and optional Stripe billing.
This powers syncbins.com. Most self-hosters should keep TENANT_MODE=single.
What you get
Section titled “What you get”- Subdomain routing —
Host: slug.yourservice.comresolves to a tenant row. - Public signup —
POST /api/signupon the apex domain creates a tenant and returns the tenant URL. - On-demand TLS — Caddy asks
GET /api/tls-check?domain=…before issuing a cert for a subdomain. - Stripe (optional) —
STRIPE_BILLING_ENABLED=trueactivates checkout on signup and webhooks for subscription state. - Per-tenant storage accounting —
storage_byteson the tenant row; enforce caps withSTORAGE_CAP_BYTES.
What you don’t get
Section titled “What you don’t get”- Multi-user inside one tenant — still one recovery phrase per subdomain.
- Cross-tenant sharing.
- Compliance packaging (GDPR tooling, audit logs) — you operate the service.
Architecture (actual)
Section titled “Architecture (actual)” slug.yourservice.com ──► Caddy (TLS) ──► Fastify SyncBins :8080 │ SQLite (one file) tenant_id on all rows │ R2 / Azure / local blobsUnlike some SaaS designs, tenants share one SQLite database with a tenant_id column — not separate db.sqlite files per slug. Blob paths are scoped by storage adapter configuration.
Conceptual comparison: Multi-tenant vs single.
-
DNS — Wildcard
*.yourservice.comand apexyourservice.com→ your server IP. -
Caddyfile — Use on-demand TLS with the ask hook (bundled example in
App/docker/Caddyfile):{on_demand_tls {ask http://syncbins:8080/api/tls-checkinterval 2mburst 5}}*.{$DOMAIN}, {$DOMAIN} {tls { on_demand }reverse_proxy syncbins:8080} -
Environment — in
docker/.env:DOMAIN=yourservice.comTENANT_MODE=multiBASE_DOMAIN=yourservice.comBLOB_URL_SECRET=…STORAGE_KIND=local # or r2 / azure# Optional billingSTRIPE_BILLING_ENABLED=trueSTRIPE_SECRET_KEY=sk_live_…STRIPE_WEBHOOK_SECRET=whsec_…STRIPE_PRICE_ID=price_… -
Start compose —
docker compose up -dfromApp/docker/. -
Create a tenant — signup via UI on apex or API:
Terminal window curl -X POST https://yourservice.com/api/signup \-H "Content-Type: application/json" \-d '{"email":"you@example.com","subdomain":"gavin"}'Response includes the tenant URL (e.g.
https://gavin.yourservice.com). Open it and complete onboarding as the first device. -
TLS check — first visit to
https://gavin.yourservice.comtriggers Caddy →/api/tls-check?domain=gavin.yourservice.com→ 200 only if tenant exists.
Stripe (optional)
Section titled “Stripe (optional)”When STRIPE_BILLING_ENABLED=true:
- Signup may redirect through Stripe Checkout before the tenant is active.
- Webhook:
POST /api/billing/webhook(configure in Stripe dashboard). - Customer portal:
GET /api/billing/portalfrom a paired device.
Inactive tenants past grace return 402 payment_required on API routes (see tenant middleware).
Exact event list and suspension timing — verify in App/server/src/routes/ before production; do not rely on undocumented grace periods.
Operations
Section titled “Operations”- Backups: Snapshot the whole SQLite file + blob store — all tenants together. See Backups.
- Logs:
docker compose logs syncbins— includetenant_idfrom request context when debugging. - Quota: Set
STORAGE_CAP_BYTESper deployment;0= unlimited (self-host default).