Self-hosting & deploying in your environment
This page covers running CRTX beyond a laptop — from the default managed deployment (Railway + Vercel) to running it inside a customer's own infrastructure (single-tenant, VPC, on-prem), with notes on bring-your-own-keys, data residency, and white-labeling.
CRTX has three runtime processes and four external dependencies:
Processes External services
───────── ─────────────────
1. API (uvicorn/FastAPI) Supabase (Postgres + Auth + Storage)
2. Worker (arq) Pinecone (vector index)
3. Frontend (Next.js) OpenAI (LLM + vision + embeddings)
Redis (job queue / rate limiting)
Cohere (optional, reranking)
The single most important rule: the worker is not optional. It processes ingestion and async eval scoring. Any deployment must run process #2 alongside #1.
#Option A — Managed (default): Railway + Vercel
The reference deployment:
- Backend (API + worker) → Railway. Deploy the
backend/service.runtime.txtpins Python 3.13;requirements.txtinstalls deps.- Run two processes. The
Procfiledefines theweb:process (the API). You must also run the worker as a separate Railway service/command:arq app.worker.WorkerSettings. If you skip it, uploads sit inqueuedforever and scores never appear. (This is called out in the scaling audit.) - Add a Redis plugin/service and set
REDIS_URL. - Set all backend env vars from Configuration.
- Frontend → Vercel. Deploy
frontend/, setNEXT_PUBLIC_*env vars, pointNEXT_PUBLIC_API_URLat the Railway backend URL, and add that Vercel domain to the backend'sALLOWED_ORIGINS. - Supabase + Pinecone are managed SaaS in this model.
#Option B — Containerized (portable): Docker Compose
To run CRTX as self-contained containers — the basis for VPC/on-prem and the listed roadmap item "Docker Compose for one-command local setup":
# docker-compose.yml (illustrative — adapt to your images)
services:
redis:
image: redis:7-alpine
ports: ["6379:6379"]
api:
build: ./backend
command: uvicorn app.main:app --host 0.0.0.0 --port 8000 --workers 2
env_file: ./backend/app/.env
depends_on: [redis]
ports: ["8000:8000"]
worker: # REQUIRED — ingestion + eval scoring
build: ./backend
command: arq app.worker.WorkerSettings
env_file: ./backend/app/.env
depends_on: [redis]
frontend:
build: ./frontend
env_file: ./frontend/.env.local
ports: ["3000:3000"]
Notes:
apiandworkershare the same image and env; only the command differs.- Run the API with
--workers Nfor concurrency (see Operations). - Supabase/Pinecone are still external in this setup. To make it fully self-contained, see "Data residency" below.
#Option C — Single-tenant in a customer's environment (VPC / on-prem)
For customers who require CRTX to run in their own cloud account or data center:
- One isolated deployment per customer. Give each customer a dedicated stack (their own API+worker+Redis+DB+index). This is the cleanest data-isolation story and simplifies compliance ("your data never leaves your account").
- Bring-your-own-keys. Point the deployment at the customer's own Supabase project (or self-hosted Postgres/Storage), their own Pinecone (or a self-hosted vector store), and their own OpenAI/Azure OpenAI account. All of this is just env configuration (Configuration) — no code changes.
- Network posture. Run inside the customer's VPC; expose only the frontend (and, if used, the API-key surface) through their ingress/WAF. The worker and Redis stay private.
- Secrets. Load env from the customer's secret manager (AWS Secrets Manager,
GCP Secret Manager, Vault) rather than a committed
.env. - Egress. By default CRTX makes outbound calls to OpenAI/Pinecone/Cohere. For restricted networks, allow-list those endpoints, or use in-region/self-hosted substitutes (below).
#Data residency & fully self-contained deployments
Depending on the customer's requirements, swap managed dependencies for in-region or self-hosted equivalents:
| Dependency | Managed default | Self-hosted / in-region option | Effort |
|---|---|---|---|
| LLM + embeddings + vision | OpenAI | Azure OpenAI (same models, regional/compliance controls) — configure the OpenAI client's base URL/keys | Low (config/client) |
| Vector store | Pinecone (cloud) | pgvector in the customer's Postgres, or a self-hosted vector DB | Medium (a vector_store adapter) |
| Postgres + Auth + Storage | Supabase (cloud) | Self-hosted Supabase (it ships as containers) or split Postgres/GoTrue/object storage | Medium (ops) |
| Job queue | Redis (managed) | Redis container in the same network | Low |
| Reranking | Cohere (cloud) | Leave disabled, or a self-hosted cross-encoder | Low |
The abstractions that make this tractable: vector access is centralized in
app/db/vector_store.py, auth is a single dependency in app/auth.py, and every
provider is chosen by env. A pgvector swap is the largest lift (reimplement
upsert/query/fetch against the same interface).
#White-labeling & multi-tenant SaaS
Two ways to serve multiple customers:
- Multi-tenant (shared) SaaS — the current shape. One deployment serves all
users; isolation is logical (per-user ownership + per-collection Pinecone
namespaces). Fastest to operate; suitable when customers accept a shared control
plane. Before scaling this, apply the fixes in the scaling audit
(indexes, the
list_usersfull-table read, uvicorn workers). - Single-tenant — Option C above; one stack per customer. Higher operational overhead, strongest isolation and residency story.
For white-labeling the UI, drive branding (logo, name, colors, legal links) from environment/config in the frontend so each deployment can be re-skinned without a code fork.
#Pre-flight checklist
- API and worker both running.
-
REDIS_URLreachable from both. - Pinecone index is 1024-dim / cosine and matches
EMBEDDING_DIMENSIONS. - Supabase
documents(private) andimages(public) buckets exist. - All application tables + observability tables created.
-
ALLOWED_ORIGINSincludes the exact frontend origin. - Service-role key is server-side only; anon key only in the frontend.
- Recommended DB indexes applied (see scaling audit).
- Secrets loaded from a secret manager, not committed.
- API run with
--workers N(or multiple replicas) for production concurrency.
Next: Operations & scaling →