[{"data":1,"prerenderedAt":567},["ShallowReactive",2],{"-docs-deploy-deployment-modes":3,"-docs-deploy-deployment-modes-surround":556},{"id":4,"title":5,"body":6,"description":546,"extension":547,"links":548,"meta":549,"navigation":550,"path":552,"seo":553,"stem":554,"__hash__":555},"docs\u002Fdocs\u002F3.deploy\u002F1.deployment-modes.md","Deployment modes",{"type":7,"value":8,"toc":538},"minimark",[9,39,46,51,181,185,192,208,215,219,230,390,393,473,477,484,488,514,518,534],[10,11,12,13,17,18,22,23,26,27,30,31,34,35,38],"p",{},"TheSpider ships as ",[14,15,16],"strong",{},"one image, two modes",". The same server image runs as the multi-tenant SaaS (",[19,20,21],"code",{},"DEPLOYMENT_MODE=saas",") or as a single-tenant ",[14,24,25],{},"on-prem"," install (",[19,28,29],{},"DEPLOYMENT_MODE=onprem","). The image serves the ",[19,32,33],{},"\u002Fv1"," API, the Better-Auth endpoints, ",[14,36,37],{},"and"," the web SPA from one origin, and runs Drizzle migrations on boot.",[10,40,41,42,45],{},"Running our own multi-tenant instance is the on-prem artifact's continuous integration test — the modes cannot drift apart because there is ",[14,43,44],{},"only one artifact",".",[47,48,50],"h2",{"id":49},"compare-the-modes","Compare the modes",[52,53,54,69],"table",{},[55,56,57],"thead",{},[58,59,60,63,66],"tr",{},[61,62],"th",{},[61,64,65],{},"SaaS (multi-tenant)",[61,67,68],{},"On-prem (single-tenant)",[70,71,72,87,98,119,130,141,152,166],"tbody",{},[58,73,74,78,84],{},[75,76,77],"td",{},"Server",[75,79,80,83],{},[19,81,82],{},"thespider.xyz",", run by us",[75,85,86],{},"customer-run Docker Compose (bundled Postgres or BYO)",[58,88,89,92,95],{},[75,90,91],{},"Orgs",[75,93,94],{},"many",[75,96,97],{},"exactly one, created at first boot (same code path, same RLS)",[58,99,100,103,113],{},[75,101,102],{},"Entitlements",[75,104,105,108,109,112],{},[19,106,107],{},"subscriptions"," + ",[19,110,111],{},"plan_limits"," (Stripe)",[75,114,115,116],{},"claims in the ",[14,117,118],{},"license JWT",[58,120,121,124,127],{},[75,122,123],{},"Object storage",[75,125,126],{},"our S3\u002FR2",[75,128,129],{},"customer-supplied S3-compatible creds (MinIO works)",[58,131,132,135,138],{},[75,133,134],{},"AI dedup key",[75,136,137],{},"platform key",[75,139,140],{},"customer-supplied key\u002Fendpoint (env)",[58,142,143,146,149],{},[75,144,145],{},"Web auth",[75,147,148],{},"Better Auth (OAuth + OIDC SSO)",[75,150,151],{},"Better Auth (email\u002Fpassword + OIDC SSO)",[58,153,154,157,163],{},[75,155,156],{},"Billing",[75,158,159,160],{},"Stripe metering on ",[19,161,162],{},"usage_events",[75,164,165],{},"flat license; caps enforced from JWT claims",[58,167,168,171,176],{},[75,169,170],{},"Client config",[75,172,173],{},[19,174,175],{},"server = \"https:\u002F\u002Fthespider.xyz\"",[75,177,178],{},[19,179,180],{},"server = \"https:\u002F\u002Fspider.corp.example\"",[47,182,184],{"id":183},"pick-the-entitlement-source","Pick the entitlement source",[10,186,187,188,191],{},"Everything that gates features or quotas calls a single ",[14,189,190],{},"entitlements resolver"," interface with two implementations:",[193,194,195,202],"ul",{},[196,197,198,201],"li",{},[14,199,200],{},"Subscription-backed"," (SaaS) — reads Stripe.",[196,203,204,207],{},[14,205,206],{},"License-backed"," (on-prem) — reads JWT claims.",[10,209,210,211,214],{},"Nothing outside the SaaS billing module imports Stripe, and the core boots with ",[14,212,213],{},"no"," Stripe \u002F OAuth \u002F webhook modules configured.",[47,216,218],{"id":217},"understand-licensing-on-prem","Understand licensing (on-prem)",[10,220,221,222,225,226,229],{},"A license is a signed JWT (Ed25519) minted by mothership-side tooling (",[19,223,224],{},"mothership\u002Fmint-license.ts"," — ",[14,227,228],{},"never"," in the image):",[231,232,237],"pre",{"className":233,"code":234,"language":235,"meta":236,"style":236},"language-json shiki shiki-themes material-theme-lighter material-theme material-theme-palenight","{ \"sub\": \"customer\", \"plan\": \"...\", \"seats\": 10, \"max_projects\": 5,\n  \"ai_dedup\": true, \"features\": [], \"iat\": 0, \"exp\": 0, \"grace_days\": 14 }\n","json","",[19,238,239,320],{"__ignoreMap":236},[240,241,244,248,251,255,258,261,263,267,269,272,274,277,279,281,283,286,288,290,292,295,297,299,303,305,307,310,312,314,317],"span",{"class":242,"line":243},"line",1,[240,245,247],{"class":246},"sMK4o","{",[240,249,250],{"class":246}," \"",[240,252,254],{"class":253},"spNyl","sub",[240,256,257],{"class":246},"\"",[240,259,260],{"class":246},":",[240,262,250],{"class":246},[240,264,266],{"class":265},"sfazB","customer",[240,268,257],{"class":246},[240,270,271],{"class":246},",",[240,273,250],{"class":246},[240,275,276],{"class":253},"plan",[240,278,257],{"class":246},[240,280,260],{"class":246},[240,282,250],{"class":246},[240,284,285],{"class":265},"...",[240,287,257],{"class":246},[240,289,271],{"class":246},[240,291,250],{"class":246},[240,293,294],{"class":253},"seats",[240,296,257],{"class":246},[240,298,260],{"class":246},[240,300,302],{"class":301},"sbssI"," 10",[240,304,271],{"class":246},[240,306,250],{"class":246},[240,308,309],{"class":253},"max_projects",[240,311,257],{"class":246},[240,313,260],{"class":246},[240,315,316],{"class":301}," 5",[240,318,319],{"class":246},",\n",[240,321,323,326,329,331,333,336,338,341,343,345,348,350,353,355,357,360,362,364,367,369,371,373,375,377,380,382,384,387],{"class":242,"line":322},2,[240,324,325],{"class":246},"  \"",[240,327,328],{"class":253},"ai_dedup",[240,330,257],{"class":246},[240,332,260],{"class":246},[240,334,335],{"class":246}," true,",[240,337,250],{"class":246},[240,339,340],{"class":253},"features",[240,342,257],{"class":246},[240,344,260],{"class":246},[240,346,347],{"class":246}," [],",[240,349,250],{"class":246},[240,351,352],{"class":253},"iat",[240,354,257],{"class":246},[240,356,260],{"class":246},[240,358,359],{"class":301}," 0",[240,361,271],{"class":246},[240,363,250],{"class":246},[240,365,366],{"class":253},"exp",[240,368,257],{"class":246},[240,370,260],{"class":246},[240,372,359],{"class":301},[240,374,271],{"class":246},[240,376,250],{"class":246},[240,378,379],{"class":253},"grace_days",[240,381,257],{"class":246},[240,383,260],{"class":246},[240,385,386],{"class":301}," 14",[240,388,389],{"class":246}," }\n",[10,391,392],{},"Validation rules:",[193,394,395,414,424,441,461],{},[196,396,397,398,401,402,405,406,409,410,413],{},"The server validates at ",[14,399,400],{},"boot and daily",", against public keys ",[14,403,404],{},"embedded in the image",", optionally refreshed from ",[19,407,408],{},"https:\u002F\u002Fapi.thespider.io\u002F.well-known\u002Fjwks.json",". Embedded keys make validation ",[14,411,412],{},"air-gap-safe","; the JWKS URL exists for rotation.",[196,415,416,419,420,423],{},[14,417,418],{},"Missing license"," → the instance boots ",[14,421,422],{},"read-only"," (reads\u002Fexports only).",[196,425,426,429,430,432,433,436,437,440],{},[14,427,428],{},"Valid license"," → caps come from JWT claims. Exceeding ",[19,431,309],{}," returns ",[19,434,435],{},"403 max_projects_exceeded","; ",[19,438,439],{},"ai_dedup:false"," keeps dedupe heuristics-only.",[196,442,443,449,450,452,453,456,457,460],{},[14,444,445,446],{},"Past ",[19,447,448],{},"exp + grace_days"," → the instance goes ",[14,451,422],{},": claims, results\u002Fimports, campaign-creation, config and triage writes return ",[19,454,455],{},"403 read_only_license",", while ",[14,458,459],{},"all reads and exports keep serving",". We don't hold your data hostage.",[196,462,463,464,468,469,472],{},"A boot with a ",[465,466,467],"em",{},"malformed\u002Fbad-signature"," license ",[14,470,471],{},"fails loudly"," instead of degrading.",[47,474,476],{"id":475},"keep-methodology-current","Keep methodology current",[10,478,479,480,483],{},"On-prem instances pull methodology\u002Fconfig updates from the mothership using the license JWT as auth (",[19,481,482],{},"GET \u002Fv1\u002Fmethod-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.",[47,485,487],{"id":486},"choose-your-deploy-target","Choose your deploy target",[193,489,490,498,505],{},[196,491,492,497],{},[493,494,496],"a",{"href":495},"\u002Fdocs\u002Fdeploy\u002Fonprem","On-prem (Docker Compose \u002F Helm)"," — the standard single-tenant install.",[196,499,500,504],{},[493,501,503],{"href":502},"\u002Fdocs\u002Fdeploy\u002Fairgap","Air-gap"," — fully offline, no phone-home.",[196,506,507,513],{},[493,508,512],{"href":509,"rel":510},"https:\u002F\u002Fgitlab.com\u002Fdragonstone-labs\u002Fthespider-saas\u002F-\u002Fblob\u002Fmain\u002Fdeploy\u002Frailway.md",[511],"nofollow","Railway"," — the SaaS host target.",[47,515,517],{"id":516},"next-steps","Next steps",[193,519,520,527],{},[196,521,522,526],{},[493,523,525],{"href":524},"\u002Fdocs\u002Freference\u002Fenv-vars","Environment reference"," — every knob.",[196,528,529,533],{},[493,530,532],{"href":531},"\u002Fdocs\u002Fguide\u002Fdata-handling","Data handling"," — isolation, retention, right to delete.",[535,536,537],"style",{},"html pre.shiki code .sMK4o, html code.shiki .sMK4o{--shiki-light:#39ADB5;--shiki-default:#89DDFF;--shiki-dark:#89DDFF}html pre.shiki code .spNyl, html code.shiki .spNyl{--shiki-light:#9C3EDA;--shiki-default:#C792EA;--shiki-dark:#C792EA}html pre.shiki code .sfazB, html code.shiki .sfazB{--shiki-light:#91B859;--shiki-default:#C3E88D;--shiki-dark:#C3E88D}html pre.shiki code .sbssI, html code.shiki .sbssI{--shiki-light:#F76D47;--shiki-default:#F78C6C;--shiki-dark:#F78C6C}html .light .shiki span {color: var(--shiki-light);background: var(--shiki-light-bg);font-style: var(--shiki-light-font-style);font-weight: var(--shiki-light-font-weight);text-decoration: var(--shiki-light-text-decoration);}html.light .shiki span {color: var(--shiki-light);background: var(--shiki-light-bg);font-style: var(--shiki-light-font-style);font-weight: var(--shiki-light-font-weight);text-decoration: var(--shiki-light-text-decoration);}html .default .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html.dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}",{"title":236,"searchDepth":322,"depth":322,"links":539},[540,541,542,543,544,545],{"id":49,"depth":322,"text":50},{"id":183,"depth":322,"text":184},{"id":217,"depth":322,"text":218},{"id":475,"depth":322,"text":476},{"id":486,"depth":322,"text":487},{"id":516,"depth":322,"text":517},"One Docker image runs as SaaS or on-prem — pick your mode","md",null,{},{"icon":551},"i-lucide-boxes","\u002Fdocs\u002Fdeploy\u002Fdeployment-modes",{"title":5,"description":546},"docs\u002F3.deploy\u002F1.deployment-modes","eHNbQ3lG5OpNvtMqrLCuKEUNJwAETZc3lPKykUdPHdA",[557,562],{"title":558,"path":531,"stem":559,"description":560,"icon":561,"children":-1},"Data handling & privacy","docs\u002F2.guide\u002F6.data-handling","Exactly what crosses the machine boundary and how secrets are protected","i-lucide-shield-check",{"title":563,"path":495,"stem":564,"description":565,"icon":566,"children":-1},"On-prem deploy","docs\u002F3.deploy\u002F2.onprem","Install TheSpider on-prem with Docker Compose or Helm","i-lucide-hard-drive-download",1783955419617]