Self-hosting
Pellucid runs on your laptop, on a VPS, or behind your own load balancer. Three components, two databases, one secret file.
The shipped stack is:
- API — FastAPI + LangGraph + LiteLLM + spaCy. Python 3.12. Default port
8000. - Web — Next.js 15. Default port
3000. - Storage — SQLite by default (zero-config) or Postgres + pgvector for production. Optional Neo4j 5 for the ontology layer.
Local development
The fastest path. SQLite, no Neo4j, env-var BYOK.
# Prereqs: Python 3.12+, Node 20+, pnpm 9+
git clone https://github.com/ashlr-ai/pellucid
cd pellucid
# JS deps
pnpm install
# Python deps + spaCy model
cd apps/api
uv sync
uv run python -m spacy download en_core_web_sm
# LLM key — see /docs/byok for all four providers
cp .env.example .env
# edit .env: set XAI_API_KEY (or another provider)
# Boot both servers from the repo root
cd ../..
pnpm dev:api # http://localhost:8000
pnpm dev:web # http://localhost:3000 (in a second terminal)SQLite gives you a single-file database at apps/api/app.db. Delete the file to reset everything; commit migrations to Postgres when you outgrow it.
Postgres + pgvector
For production. The pgvector image bundles Postgres 16 with the vector extension already enabled, so you skip the CREATE EXTENSION permission dance. The compose file is in apps/api/.
docker compose -f docker-compose.postgres.yml up -d postgres
# Verify the container is healthy
docker compose -f docker-compose.postgres.yml psPoint the API at it via env:
PELLUCID_DB_URL=postgresql://pellucid:pellucid@localhost:5432/pellucidRun the schema migrations:
uv run alembic upgrade head
# To create a new migration after a model change:
uv run alembic revision --autogenerate -m "describe the change"create_all()on boot for a frictionless first run. Postgres is migration-only — Alembic owns the schema. Don’t mix the two on the same database.Neo4j (optional)
The ontology layer is the only consumer of Neo4j. If you don’t plan to ingest approved-reference documents, skip it — every ontology endpoint responds without it (extraction runs in-memory and the results aren’t persisted).
docker compose -f docker-compose.neo4j.yml up -d
# Browser console: http://localhost:7474
# Bolt: bolt://localhost:7687
# Default credentials (DEV ONLY): neo4j / pellucid-devConfigure the API to use it:
NEO4J_URI=bolt://localhost:7687
NEO4J_USER=neo4j
NEO4J_PASSWORD=pellucid-devThe compose file pre-installs APOC and gives Neo4j a 1 GB heap. Tune NEO4J_server_memory_heap_max__size upward in production — the default is fine for a dev box but tight for any real ontology.
Compose them all
Run the full local stack — Postgres, Neo4j — with one command. Either invoke both compose files or merge them into your own docker-compose.override.yml:
docker compose \
-f docker-compose.postgres.yml \
-f docker-compose.neo4j.yml \
up -d
# Tail logs
docker compose -f docker-compose.postgres.yml -f docker-compose.neo4j.yml logs -fThe API and web app stay outside Compose for now — running them with pnpm dev gives faster reload than rebuilding containers on every change. For production, see the production deploy section below.
Environment variables
| Variable | Default | Notes |
|---|---|---|
PELLUCID_ENV | development | Set to production for JSON-line logs. |
PELLUCID_DB_URL | SQLite | Postgres URL when self-hosting. |
PELLUCID_CORS_ORIGINS | http://localhost:3000 | Comma-separated. Add the prod web origin. |
PELLUCID_LLM_MODEL | xai/grok-4 | Default model when no BYOK row exists. |
XAI_API_KEY / ANTHROPIC_API_KEY / OPENAI_API_KEY / OPENROUTER_API_KEY | — | One required. See BYOK. |
NEO4J_URI / NEO4J_USER / NEO4J_PASSWORD | — | Optional. Enables the ontology graph. |
Production — API
The repo ships a multi-stage Dockerfile that bakes the spaCy model into the image, plus a railway.toml for Railway-native deploys. The same image runs anywhere that speaks OCI containers (Fly.io, Render, your own k8s cluster).
docker build -t pellucid/api:0.1.0 .
docker run --rm -p 8000:8000 \
-e PELLUCID_ENV=production \
-e PELLUCID_DB_URL=postgresql://... \
-e PELLUCID_CORS_ORIGINS=https://pellucid.example.com \
-e ANTHROPIC_API_KEY=sk-ant-... \
-v /etc/pellucid:/secrets:ro \
pellucid/api:0.1.0Mount .pellucid_key from your secrets manager — the file holds the per-install Fernet key used to encrypt BYOK provider rows and webhook secrets at rest. Losing it breaks every encrypted row; leaking it lets an attacker decrypt them.
Production logging
PELLUCID_ENV=production switches the logger to one JSON object per line — friendly to Datadog, Loki, Cloud Logging, anything that ingests JSON. Pretty logs in dev; structured logs in prod, no flag choreography.
{"ts":"2026-05-09T15:32:01.412Z","level":"INFO","logger":"uvicorn.error","msg":"Started server process [42]"}Production — Web
The web app is a stock Next.js 15 build. Deploy to Vercel, Cloud Run, Fly, or self-host behind any reverse proxy.
pnpm build
pnpm start # http://localhost:3000
# OR: containerize with whatever Next.js Dockerfile fits your platformThe web app expects the API origin in two env vars:
# The API origin the browser will hit
NEXT_PUBLIC_API_URL=https://api.pellucid.example.com
# Used in metadata, OG/Twitter cards, sitemap
NEXT_PUBLIC_SITE_URL=https://pellucid.example.comProduction checklist
- Postgres, not SQLite. SQLite is safe for one user on one box; concurrent writes will block.
- Set
PELLUCID_ENV=productionso logs are JSON-line and your aggregator can parse them. - Tighten CORS. Replace the dev wildcard with the exact web origin via
PELLUCID_CORS_ORIGINS. Keep thechrome-extension://*regex if you use the browser extension (or pin to the published extension id). - Mount
.pellucid_keyfrom your secrets manager. Back it up out-of-band. Rotation is manual today — re-saving every BYOK row + webhook re-encrypts under the new key. - HTTPS terminates upstream. Run the API behind your reverse proxy of choice. Pellucid does not terminate TLS itself.
- Plan for retries. The webhook dispatcher is at-most- once and times out at five seconds — if the receiver is critical, mirror the payload into a queue receiver-side.
- Decide on auth. The Session 1 API runs unauthenticated. Mint API keys via
/api/v1/integrations/api-keysnow so they’re ready for the auth middleware.
Sizing
For a single-tenant install with a handful of users:
- API.1 vCPU, 1 GB RAM. spaCy and LangGraph fit comfortably; LLM calls are I/O-bound, not CPU-bound.
- Web.0.5 vCPU, 512 MB RAM. Static-mostly with a handful of API proxy routes.
- Postgres.1 vCPU, 1 GB RAM, 10 GB storage to start. Findings + debate transcripts dominate row count.
- Neo4j.1 vCPU, 2 GB RAM. Skip entirely if you don’t use the ontology layer.
Scale agent throughput by raising the LLM provider’s rate limit. Pellucid runs four agents concurrently per analysis; if your provider caps at 60 RPM you’ll cap around 15 analyses per minute before rate-limit retries kick in.
Upgrading
- Pull, install, migrate.bash
git pull pnpm install cd apps/api && uv sync && uv run alembic upgrade head - Watch the changelog. Webhook payload schema bumps are breaking;
schema_versionreflects the contract. - Smoke-test the LLM key. Run a synthetic analyze right after a deploy — provider rate limits and key revocations are the most common silent failures.