A self-hosted web application that lets multiple authenticated users upload documents and chat with an AI that answers questions grounded in their content. Built for production — async, Dockerized, and configurable to use a local Ollama model or any cloud LLM provider.
- Organize documents into named, isolated libraries — keep unrelated corpora separate (e.g. "Spinal Cord Injury", "Jones Act Cases")
- Upload PDFs, text files, or web URLs into any library — or select an entire folder for batch ingestion
- Chat with an AI assistant that searches one or more libraries simultaneously, answering questions using only their documents, with clickable source citations and one-click document download
- Admin-managed libraries — only the admin adds, removes, or creates collections; all authenticated users can pick any combination of libraries and query across them
- Switch between local (Ollama) and cloud LLM providers (OpenAI, Anthropic, or any OpenAI-compatible endpoint) from the admin UI
- MCP server included —
mcp_server.pyconnects Claude Desktop or Claude Code directly to the knowledge base for querying, ingestion, and management
| Component | Technology |
|---|---|
| Backend | FastAPI + Gunicorn (async workers) |
| Vector database | Qdrant (isolated per-library collections) |
| Auth database | PostgreSQL + SQLAlchemy |
| Job queue | Redis + RQ (background ingestion) |
| Embeddings | BAAI/bge-large-en-v1.5 via sentence-transformers |
| LLM | Ollama (bundled container) or OpenAI / Anthropic / generic |
| Frontend | Jinja2 templates + custom Fathom CSS (no framework dependency) |
- Docker and Docker Compose v2+
- 4 GB+ RAM and 10 GB+ free disk space (for models and embeddings)
Ollama runs inside the stack as its own container — no host installation needed. After the stack is up, pick and pull a model from the admin UI at /admin/llm-settings.
$ export DATA_ROOT=/storage/rag
$ sudo mkdir -p "$DATA_ROOT"/{postgres,qdrant,redis,uploads,ollama}The stack binds named Docker volumes to these paths. Override DATA_ROOT in your .env if /storage/rag doesn't suit your setup.
$ git clone https://github.com/jemplayer82/RAG.git
$ cd RAG
$ cp .env.example .env
# Only POSTGRES_PASSWORD is required — edit .env and set it$ docker compose up -d
$ curl http://localhost:8000/api/healthBrowse to http://localhost:8000 and register an account. The first registered user becomes admin automatically.
Note
A cloned repo auto-loads docker-compose.override.yml, which builds the image locally. To use the pre-built published image instead, run docker compose -f docker-compose.yml up -d.
Make sure the host directories exist first (see Prerequisites), then:
- Go to Stacks → Add stack, name it
rag - Paste the contents of
docker-compose.ymlinto the web editor — do not includedocker-compose.override.yml(Portainer's paste mode has no Dockerfile access) - Add environment variables:
POSTGRES_PASSWORD— requiredRAG_PORT— optional, defaults to8000OPENAI_API_KEY/ANTHROPIC_API_KEY— only if using a cloud LLM
- Deploy — Portainer pulls
ghcr.io/jemplayer82/rag:latest
- Register at
/register— the first account is automatically promoted to admin - Log in
- Visit
/admin/llm-settings— select a provider and pull or configure a model - Go to Libraries and create at least one library (e.g. "My Documents")
- Go to Add Sources, select the library, and upload a PDF or enter a URL
- Wait for the ingestion job to finish (progress is visible in the upload UI)
- Open Chat, pick a library from the dropdown, and start asking questions
Note
On a fresh install, a starter library named "My Library" is created automatically when the first admin registers, so you can skip step 4 and go straight to adding sources.
Each library is an independent Qdrant collection — documents in different libraries never cross-contaminate. The admin creates and manages libraries; any authenticated user can choose which one to query.
Common workflows:
- Admin creates a "Spinal Cord Injury" library and a "Jones Act" library
- Uploads the relevant documents to each
- Users pick a library in the Chat header before asking questions — only that library's documents are searched
Persistence: Library data (vectors, documents, uploaded files) is stored on bind-mounted host paths and survives container rebuilds, image updates, and docker compose down.
| Variable | Required | Description |
|---|---|---|
POSTGRES_PASSWORD |
Yes | PostgreSQL password |
JWT_SECRET |
No | Auto-generated on first boot and persisted to /storage/rag/uploads/.secrets.env |
ENCRYPTION_KEY |
No | Auto-generated on first boot (same persistence as above) |
RAG_PORT |
No | Host port for the web UI (default: 8000) |
DATA_ROOT |
No | Host base directory for data (default: /storage/rag) |
LLM_PROVIDER |
No | ollama (default), openai, anthropic, or generic |
LLM_MODEL |
No | Model name — leave blank to configure from the admin UI |
LLM_BASE_URL |
No | Fixed to the in-stack Ollama (http://ollama:11434) in docker-compose.yml. For a remote Ollama or cloud endpoint, set the Base URL per-provider in the admin UI instead. |
OPENAI_API_KEY |
No | Required only when using OpenAI |
ANTHROPIC_API_KEY |
No | Required only when using Anthropic |
EMBED_MODEL |
No | HuggingFace embedding model (default: BAAI/bge-large-en-v1.5) |
EMBED_DEVICE |
No | cpu or cuda (default: cpu) |
CHUNK_SIZE |
No | Token chunk size for ingestion (default: 600) |
See .env.example for the full list with descriptions.
Browser
│
▼
FastAPI app (:8000) ← the only host-published service
├── PostgreSQL ← users, libraries, documents, job records
├── Qdrant ← isolated per-library vector collections
├── Redis → rag-worker ← background document ingestion
└── Ollama ← local LLM inference
Only the rag container exposes a port to the host. All other services are reachable only from inside the Docker network.
$ git clone https://github.com/jemplayer82/RAG.git /opt/rag
$ cd /opt/rag
$ cp .env.example .env && nano .env
$ docker compose up -dFor HTTPS, place a TLS terminator (Caddy, Cloudflare Tunnel, or nginx) in front of port 8000. TLS is intentionally left to the host layer.
CI/CD is configured in .github/workflows/deploy.yml. Add VPS_HOST, VPS_USER, and VPS_SSH_KEY to your GitHub repository secrets to enable auto-deploy on push to master.
$ python -m venv venv && source venv/bin/activate
$ pip install -r requirements.txt
$ cp .env.example .env
# Add OLLAMA_BASE_URL=http://localhost:11434 to .env if Ollama runs on the host
# Requires local PostgreSQL, Qdrant, and Redis
$ uvicorn app_fastapi:app --reload --port 8000mcp_server.py (included in this repo) exposes the RAG knowledge base as an MCP server. Connect it to Claude Desktop or Claude Code to query, ingest, and manage the knowledge base without opening the web UI — the server talks to the deployed RAG API over HTTP, so no changes to the Docker stack are needed.
pip install -r requirements-mcp.txt # mcp + httpx — kept out of the Docker imageRequires Python 3.10+ on the machine running Claude (not inside Docker).
Edit ~/Library/Application Support/Claude/claude_desktop_config.json (macOS) or %APPDATA%\Claude\claude_desktop_config.json (Windows):
{
"mcpServers": {
"rag": {
"command": "python",
"args": ["C:/path/to/RAG/mcp_server.py"],
"env": {
"RAG_BASE_URL": "http://YOUR_HOST:8000",
"RAG_USERNAME": "admin",
"RAG_PASSWORD": "yourpassword"
}
}
}
}claude mcp add rag -- python /path/to/RAG/mcp_server.pyThen set the env vars in .claude/mcp.json under the rag entry.
| Variable | Description |
|---|---|
RAG_BASE_URL |
Base URL of the RAG app (default: http://localhost:8000) |
RAG_USERNAME |
Username for auto-login (recommended — token self-refreshes on expiry) |
RAG_PASSWORD |
Password for auto-login |
RAG_TOKEN |
Pre-generated JWT from localStorage.rag_token (static — use creds instead) |
| Tool | Auth | Description |
|---|---|---|
query |
any user | Ask a question; searches one or all libraries |
list_libraries |
any user | List libraries with IDs and document counts |
list_documents |
any user | List documents; optional library and title filters |
add_file |
admin | Ingest a local file (PDF, TXT, DOC, DOCX) |
add_url |
admin | Ingest a web URL; optional site crawl |
get_job_status |
admin | Check ingestion job progress |
delete_document |
admin | Remove a document and its vectors |
Released under the GNU AGPL-3.0. If you run a modified version as a network service, the license requires you to make your source available to its users.
