Deployment modes
TheSpider ships as one image, two modes. The same server image runs as the multi-tenant SaaS (DEPLOYMENT_MODE=saas) or as a single-tenant on-prem install (DEPLOYMENT_MODE=onprem). The image serves the /v1 API, the Better-Auth endpoints, and the web SPA from one origin, and runs Drizzle migrations on boot.
Running our own multi-tenant instance is the on-prem artifact's continuous integration test — the modes cannot drift apart because there is only one artifact.
Compare the modes
| SaaS (multi-tenant) | On-prem (single-tenant) | |
|---|---|---|
| Server | thespider.xyz, run by us | customer-run Docker Compose (bundled Postgres or BYO) |
| Orgs | many | exactly one, created at first boot (same code path, same RLS) |
| Entitlements | subscriptions + plan_limits (Stripe) | claims in the license JWT |
| Object storage | our S3/R2 | customer-supplied S3-compatible creds (MinIO works) |
| AI dedup key | platform key | customer-supplied key/endpoint (env) |
| Web auth | Better Auth (OAuth + OIDC SSO) | Better Auth (email/password + OIDC SSO) |
| Billing | Stripe metering on usage_events | flat license; caps enforced from JWT claims |
| Client config | server = "https://thespider.xyz" | server = "https://spider.corp.example" |
Pick the entitlement source
Everything that gates features or quotas calls a single entitlements resolver interface with two implementations:
- Subscription-backed (SaaS) — reads Stripe.
- License-backed (on-prem) — reads JWT claims.
Nothing outside the SaaS billing module imports Stripe, and the core boots with no Stripe / OAuth / webhook modules configured.
Understand licensing (on-prem)
A license is a signed JWT (Ed25519) minted by mothership-side tooling (mothership/mint-license.ts — never in the image):
{ "sub": "customer", "plan": "...", "seats": 10, "max_projects": 5,
"ai_dedup": true, "features": [], "iat": 0, "exp": 0, "grace_days": 14 }
Validation rules:
- The server validates at boot and daily, against public keys embedded in the image, optionally refreshed from
https://api.thespider.io/.well-known/jwks.json. Embedded keys make validation air-gap-safe; the JWKS URL exists for rotation. - Missing license → the instance boots read-only (reads/exports only).
- Valid license → caps come from JWT claims. Exceeding
max_projectsreturns403 max_projects_exceeded;ai_dedup:falsekeeps dedupe heuristics-only. - Past
exp + grace_days→ the instance goes read-only: claims, results/imports, campaign-creation, config and triage writes return403 read_only_license, while all reads and exports keep serving. We don't hold your data hostage. - A boot with a malformed/bad-signature license fails loudly instead of degrading.
Keep methodology current
On-prem instances pull methodology/config updates from the mothership using the license JWT as auth (GET /v1/method-updates), and every image release bundles a snapshot for air-gapped installs. An active license is what keeps the methodology current — the commercial lever that survives shipping the brain into customer infrastructure.
Choose your deploy target
- On-prem (Docker Compose / Helm) — the standard single-tenant install.
- Air-gap — fully offline, no phone-home.
- Railway — the SaaS host target.
Next steps
- Environment reference — every knob.
- Data handling — isolation, retention, right to delete.