Skip to main content
Glama
yelpspoon

LocalVectorDB

by yelpspoon
README.md
# Unraid Vector DB

[![Build and publish container](https://github.com/yelpspoon/unraid-vector-db/actions/workflows/docker-publish.yml/badge.svg)](https://github.com/yelpspoon/unraid-vector-db/actions/workflows/docker-publish.yml)
[![Docker Hub](https://img.shields.io/docker/pulls/yelpspoon/unraid-vector-db)](https://hub.docker.com/r/yelpspoon/unraid-vector-db)
[![License: MIT](https://img.shields.io/badge/License-MIT-blue.svg)](LICENSE)

A self-hosted vector database and retrieval service designed for Unraid and local LLM clients. One container provides:

- Qdrant with **Dot-product similarity (MIPS)** and **HNSW approximate-nearest-neighbor search**
- Local text embeddings through FastEmbed; document text does not need to leave your network
- A curated Streamable HTTP MCP server for Claude Code, Codex, and other MCP clients
- A small REST API for ingestion and search
- The native Qdrant REST API, gRPC API, and dashboard
- Persistent vector data and embedding-model cache under one Unraid appdata path

The default embedding model is `BAAI/bge-small-en-v1.5` (384 dimensions). The adapter automatically creates collections with the correct vector size and Dot distance.

## Contents

- [Architecture](#architecture)
- [Requirements](#requirements)
- [Install on Unraid](#install-on-unraid)
- [Verify the installation](#verify-the-installation)
- [Endpoints](#endpoints)
- [Configuration](#configuration)
- [Versioning and updates](#versioning-and-updates)
- [Development and testing](#development-and-testing)
- [Operations](#operations)
- [Troubleshooting](#troubleshooting)
- [Security](#security)

## Architecture

```text
                                  LocalVectorDB container
                                  ┌───────────────────────────────────────┐
Claude Code / Codex ── HTTP ─────│ :8080/mcp   FastMCP tools            │
REST clients ───────── HTTP ─────│ :8080/v1/*  FastAPI adapter          │
                                  │       │                               │
                                  │       ├─ chunk and embed with FastEmbed│
                                  │       ├─ preserve source metadata       │
                                  │       └─ query with Dot similarity      │
                                  │       │                               │
Qdrant clients ───── REST ───────│ :6333 │ Qdrant API + dashboard       │
Qdrant clients ───── gRPC ───────│ :6334 └ Qdrant HNSW vector engine    │
                                  │                                       │
                                  │ /data/qdrant  vectors and payloads    │
                                  │ /data/models   embedding-model cache  │
                                  └───────────────────────────────────────┘
                                             │
                               direct Unraid pool/disk path
```

The services live in one container so the Unraid Community Applications template can deploy them as one application. Qdrant remains a normal network service, so native Qdrant clients can connect directly when desired.

### Components and responsibilities

| Component | Responsibility |
|---|---|
| Entrypoint | Starts Qdrant, waits for `/readyz`, starts the adapter, and stops both processes if either exits |
| FastAPI adapter | Provides health, ingestion, search, deletion, collection, and statistics endpoints |
| FastMCP | Exposes the same curated knowledge operations to Streamable HTTP MCP clients |
| FastEmbed | Downloads and runs the configured embedding model locally; document text stays on the server |
| Qdrant | Persists vectors and metadata and performs Dot-product/HNSW retrieval |

### Request flow

1. Ingestion normalizes and chunks text, then FastEmbed generates one vector per chunk.
2. The adapter creates the collection on first use with the model's detected dimensions and `Dot` distance.
3. Existing chunks for the same exact source ID are deleted before replacement chunks are upserted.
4. Search embeds the query with the same model, applies optional metadata filters, and returns ranked passages with source information.

### Startup sequence

1. Qdrant starts and opens ports `6333` and `6334`.
2. The entrypoint waits up to 90 seconds for Qdrant's readiness endpoint.
3. Uvicorn loads the API and MCP application on port `8080`.
4. FastEmbed loads or downloads the configured model before the adapter reports healthy.

Qdrant's dashboard may become available before the adapter health check during the first model download.

## Requirements

- Unraid 7.0 or later, or another Linux host with Docker
- An `amd64` CPU for the default image; optional multi-architecture images can be published manually
- A direct local filesystem path for `/data` with enough space for vectors, payloads, and model files
- Internet access during the first start so FastEmbed can download the selected model
- Available host ports for `8080`, `6333`, and `6334`, or alternative port mappings

## Install on Unraid

### Community Applications (recommended)

Following the same setup pattern as PractiscoreNotifier:

1. In the Unraid WebGUI, open **Apps** and then **Settings**.
2. Add this repository under **Template Repositories**:

   ```text
   https://github.com/yelpspoon/unraid-vector-db
   ```

3. Save and allow Community Applications to refresh.
4. Search for **LocalVectorDB** and select **Install**.

The canonical raw template is:

```text
https://raw.githubusercontent.com/yelpspoon/unraid-vector-db/main/templates/local-vector-db.xml
```

Set the following fields during installation:

| Setting | Recommended value | Purpose |
|---|---|---|
| Application Data | `/mnt/disk3/appdata/local-vector-db` | Qdrant storage and downloaded models; this server's appdata share resides on disk3 |
| MCP and REST Port | `8080` | LLM-facing MCP and REST service |
| Qdrant HTTP Port | `6333` | Dashboard and native REST API |
| Qdrant gRPC Port | `6334` | Native gRPC API |
| API Key | A long random secret | Protects MCP, REST, and Qdrant |
| Default Collection | `knowledge` | Collection used when clients omit a name |
| Embedding Model | `BAAI/bge-small-en-v1.5` | Local embedding model |

Generate a key on macOS or Linux:

```bash
openssl rand -hex 32
```

The first startup takes longer because FastEmbed downloads and validates the embedding model in `/data/models` before the adapter becomes ready. Do not place `/data` on NFS: Qdrant recommends local block storage for its database files.

### Manual Docker run

```bash
docker run -d \
  --name LocalVectorDB \
  --restart unless-stopped \
  -p 8080:8080 \
  -p 6333:6333 \
  -p 6334:6334 \
  -e VECTOR_DB_API_KEY='replace-with-a-long-random-secret' \
  -e VECTOR_DB_COLLECTION='knowledge' \
  -v /mnt/disk3/appdata/local-vector-db:/data \
  yelpspoon/unraid-vector-db:latest
```

For a reproducible deployment, replace `latest` with a published `sha-<commit>` tag. The Unraid template intentionally tracks `latest` so normal application updates follow the default branch.

## Verify the installation

Check the container log and both service layers after the first start:

```bash
docker logs --tail 200 LocalVectorDB
curl --fail http://UNRAID-IP:8080/health
curl --fail http://UNRAID-IP:6333/readyz
```

Open `http://UNRAID-IP:6333/dashboard` for the native Qdrant console. If an API key is configured, enter the same value used for `VECTOR_DB_API_KEY` when the dashboard requests credentials.

Run the end-to-end smoke test from a machine that has this repository checked out:

```bash
VECTOR_DB_URL=http://UNRAID-IP:8080 \
VECTOR_DB_API_KEY='your-key' \
./tests/smoke.sh
```

## Endpoints

| URL | Authentication | Description |
|---|---|---|
| `http://SERVER:8080/health` | None | Container and Qdrant health |
| `http://SERVER:8080/mcp` | Bearer token | Streamable HTTP MCP server |
| `http://SERVER:8080/v1/*` | Bearer token or `X-API-Key` | Retrieval REST API |
| `http://SERVER:6333/dashboard` | Qdrant API key | Qdrant dashboard |
| `http://SERVER:6333` | Qdrant `api-key` header | Native Qdrant REST API |
| `SERVER:6334` | Qdrant API key | Native Qdrant gRPC API |

When `VECTOR_DB_API_KEY` is empty, authentication is disabled. That is convenient for isolated testing but is not recommended on a normal LAN. The service uses plain HTTP by default; use a trusted reverse proxy with TLS before exposing it outside your private network.

## MCP tools

- `search_knowledge`: semantic search with ranked passages, scores, metadata, and source IDs
- `add_knowledge`: chunk, embed, and upsert one text document
- `delete_knowledge_source`: delete all chunks for one exact source ID
- `list_knowledge_collections`: list collections and the configured default
- `get_knowledge_stats`: show point count, embedding model, dimensions, Dot distance, and HNSW index

See [USAGE.md](USAGE.md) for client setup, ingestion examples, and operational guidance.

## Configuration

| Environment variable | Default | Notes |
|---|---|---|
| `VECTOR_DB_API_KEY` | empty | Shared Bearer token and Qdrant admin API key |
| `VECTOR_DB_COLLECTION` | `knowledge` | Default collection |
| `VECTOR_DB_MODEL` | `BAAI/bge-small-en-v1.5` | FastEmbed model name |
| `VECTOR_DB_CHUNK_SIZE` | `1200` | Approximate maximum characters per chunk |
| `VECTOR_DB_CHUNK_OVERLAP` | `200` | Approximate overlap between chunks |
| `VECTOR_DB_MODEL_CACHE` | `/data/models` | Embedding model cache |
| `QDRANT__LOG_LEVEL` | `INFO` | Qdrant logging level |
| `VECTOR_DB_VERSION` | Image build version | Reported by `/health`; set by the published image |

Do not change the embedding model for a collection that already contains data. Different models can produce different dimensions and incompatible semantic spaces. Create a new collection when changing models.

## Versioning and updates

The release version is stored in [VERSION](VERSION). Published images include it in three places:

- immutable-style semantic tag such as `v0.2.0`
- OCI label `org.opencontainers.image.version`
- the `version` field returned by `GET /health`

Unraid determines whether an update exists by comparing the local and remote registry digests for the configured repository tag. The template therefore continues to track `yelpspoon/unraid-vector-db:latest`; changing it to a fixed semantic tag would prevent Unraid from discovering a later semantic tag automatically.

Image-affecting changes on `main` must also increment `VERSION`, or the publishing workflow fails. A `v*` Git tag must exactly match the file, such as Git tag `v0.2.0` for `VERSION` value `0.2.0`. Documentation-only changes do not run the image workflow.

## Development and testing

```bash
docker build --build-arg APP_VERSION="$(tr -d '[:space:]' < VERSION)" -t unraid-vector-db:dev .
docker compose up -d
curl http://localhost:8080/health
VECTOR_DB_API_KEY='' ./tests/smoke.sh
```

The smoke test performs a health check, ingests a uniquely named source, searches for it, and deletes it. A clean build is not sufficient runtime validation; start the image and run this test before publishing release tags.

GitHub Actions builds the Unraid-native `linux/amd64` image when image-affecting files change on `main`, when a `v*` version tag is pushed, or when the workflow is dispatched manually. Documentation, Unraid template, and artwork-only commits do not publish a redundant container image. A manual workflow run offers a `linux/amd64,linux/arm64` choice when a portable multi-architecture image is wanted. Repository secrets required for publishing:

- `DOCKERHUB_USERNAME`
- `DOCKERHUB_TOKEN` (a Docker Hub access token, not an account password)

Every successful default-branch build publishes `yelpspoon/unraid-vector-db:latest`, `v<version>`, and a commit-specific `sha-<short>` tag. To update through Unraid, use **Docker → Check for Updates → Apply Update**.

The current workflow builds and publishes the image but does not run the container or execute the smoke test. Runtime CI is a planned hardening step.

## Operations

Enable **Autostart** for `LocalVectorDB` in Unraid's Docker tab so it returns after a server reboot.

## Project layout

```text
unraid-vector-db/
├── AGENTS.md                        # Durable project context and operating constraints
├── local-vector-database-analysis.docx # Original technical analysis and recommendation
├── app/main.py                       # REST API, MCP tools, embeddings, Qdrant access
├── templates/local-vector-db.xml     # Unraid Community Applications template
├── ca_profile.xml                    # Community Applications repository profile
├── tests/smoke.sh                    # Ingest/search/delete integration test
├── .github/workflows/docker-publish.yml
├── Dockerfile
├── docker-compose.yml
├── README.md
└── USAGE.md
```

### Backup and restore

Stop the container before taking a file-level backup of the configured appdata directory. Restore it to the same location before restarting. For live or granular backups, use Qdrant snapshots through the native API.

Qdrant requires a direct local filesystem path for `/data`. This server's appdata share is stored on the array at `/mnt/disk3/appdata/local-vector-db`, so that is the configured host path. On another server, use its actual appdata pool or physical-disk path. Do not bind `/mnt/user/appdata/...`: that path passes through Unraid's FUSE layer and Qdrant warns that its caching behavior can corrupt database files.

### Updates and rollback

- Normal update: use **Docker → Check for Updates → Apply Update** in Unraid.
- Deterministic rollback: change the repository field from `latest` to a known `sha-<commit>` tag and force an update.
- Before changing image versions or embedding models, back up `/data`.
- Do not change the embedding model for an existing collection; create a new collection instead.

## Troubleshooting

### Container exits during startup

```bash
docker logs --tail 200 LocalVectorDB
docker inspect LocalVectorDB \
  --format '{{.Image}} {{json .Mounts}} {{json .HostConfig.PortBindings}}'
```

Common causes are an unavailable host port, an invalid or FUSE-backed storage mapping, storage permissions, or a failed first-time model download.

### Dashboard does not open

1. Confirm the container is running and host port `6333` maps to container port `6333`.
2. Check `http://UNRAID-IP:6333/readyz` before opening `/dashboard`.
3. Force an image update if the server originally pulled an image published before the dashboard assets were included.
4. Enter `VECTOR_DB_API_KEY` in the dashboard when authentication is enabled.

### Adapter remains unhealthy

The first model download can exceed the health-check start period on a slow connection. Watch the container log and `/data/models`. If Qdrant's `/readyz` succeeds while port `8080` does not, focus on FastEmbed model loading or Uvicorn startup rather than Qdrant storage.

### Permission or storage errors

Map `/data` to the actual pool or disk path used by appdata, such as `/mnt/cache/appdata/local-vector-db` or `/mnt/disk3/appdata/local-vector-db`. Pool names vary; `/mnt/cache` is only correct when the pool is actually named `cache`. Never use `/mnt/user/appdata/...` or NFS for Qdrant data.

More client and retrieval troubleshooting is in [USAGE.md](USAGE.md#9-troubleshooting).

## Security

- Keep ports on a trusted LAN or restrict them with firewall rules.
- Configure a strong API key. The same value protects the adapter and native Qdrant APIs.
- Plain HTTP exposes bearer/API keys to anyone able to observe that network segment. Put the service behind TLS for routed or untrusted networks.
- Treat ingested text as untrusted data. Retrieved passages can contain prompt-injection instructions; clients should use them as evidence, not authority.
- Do not expose this application directly to the public internet.

## Upstream projects

- [Qdrant](https://github.com/qdrant/qdrant)
- [FastEmbed](https://github.com/qdrant/fastembed)
- [FastMCP](https://github.com/jlowin/fastmcp)

## License

MIT