Part of #1029.
Context
The current RAG vector store is split: sqlite-vec on the SQLite path, pgvector on the Postgres path. Both are embedded in the DB layer and are outgrown quickly — neither supports filtering, quantization, or high-cardinality search efficiently. Qdrant is a purpose-built vector database that runs as a standalone container, handles millions of embeddings, and exposes a clean REST + gRPC API. It becomes the RAG store when QDRANT_URL is set; both sqlite-vec and pgvector remain untouched as the zero-config fallbacks.
Easy on/off
# Enable:
docker compose --profile qdrant up -d
# Add to .env: QDRANT_URL=http://qdrant:6333
# Disable:
# Remove QDRANT_URL from .env — falls back to sqlite-vec or pgvector automatically
No other service depends on Qdrant. Removing the profile stops the container; unsetting the env var makes the app ignore it.
Implementation plan
docker-compose.yml
qdrant:
image: qdrant/qdrant:latest
profiles: ["qdrant"]
restart: unless-stopped
ports:
- "6333:6333" # REST API
- "6334:6334" # gRPC
volumes:
- qdrant-data:/qdrant/storage
environment:
QDRANT__SERVICE__GRPC_PORT: "6334"
src/selfhost/qdrant-vectorize.ts
New Vectorize-compatible adapter using the Qdrant REST API:
createQdrantVectorize(url: string): Vectorize
upsert(vectors) → PUT /collections/{name}/points
query(vector, topK) → POST /collections/{name}/points/search
- Collection per-repo (namespace via collection name)
- Falls back gracefully if Qdrant is unreachable (logs warning, returns empty results)
server.ts wiring
// After existing vectorize setup:
if (process.env.QDRANT_URL) {
vectorize = createQdrantVectorize(process.env.QDRANT_URL);
console.log(JSON.stringify({ event: "selfhost_vectorize", backend: "qdrant" }));
}
.env.example
# Qdrant vector database (--profile qdrant)
# QDRANT_URL=http://qdrant:6333 # enables Qdrant; omit to use sqlite-vec/pgvector
# QDRANT_COLLECTION=gittensory # collection name (default: gittensory)
Acceptance criteria
Part of #1029.
Context
The current RAG vector store is split:
sqlite-vecon the SQLite path,pgvectoron the Postgres path. Both are embedded in the DB layer and are outgrown quickly — neither supports filtering, quantization, or high-cardinality search efficiently. Qdrant is a purpose-built vector database that runs as a standalone container, handles millions of embeddings, and exposes a clean REST + gRPC API. It becomes the RAG store whenQDRANT_URLis set; bothsqlite-vecandpgvectorremain untouched as the zero-config fallbacks.Easy on/off
No other service depends on Qdrant. Removing the profile stops the container; unsetting the env var makes the app ignore it.
Implementation plan
docker-compose.yml
src/selfhost/qdrant-vectorize.ts
New
Vectorize-compatible adapter using the Qdrant REST API:createQdrantVectorize(url: string): Vectorizeupsert(vectors)→PUT /collections/{name}/pointsquery(vector, topK)→POST /collections/{name}/points/searchserver.ts wiring
.env.example
Acceptance criteria
docker compose --profile qdrant upstarts Qdrant at ports 6333/6334QDRANT_URLset → app uses Qdrant for all vector operations; logsselfhost_vectorize: qdrantQDRANT_URLunset → falls back to sqlite-vec/pgvector silentlyqdrant-vectorize.tsadapter unit tested: upsert, query, unreachable-host fallbackqdrant-datavolume added to docker-compose.yml volumes blocknpm run test:cigreen, Codecov ≥ 97% patch