Many teams want AI to answer questions from internal documents, but they cannot send those documents to a cloud service.
Legal teams have confidential case files. Clinics have patient data. Finance teams have client working papers. Manufacturers have proprietary process documentation. Some organizations operate on isolated networks.
For these teams, a cloud-first RAG stack is not an option.
I built LocalRAG to verify a simple idea:
A document question-answering system should be able to run inside a controlled environment, use local models, and return source references for every answer.
The project is open source and the current v0.1.0 release has been verified end to end.
- Repository: https://github.com/Pingredsai/local-rag
- Release: https://github.com/Pingredsai/local-rag/releases/tag/v0.1.0
What LocalRAG does
LocalRAG allows a user to upload documents and ask questions against them.
The verified flow is:
- Upload a PDF, DOCX, TXT, or Markdown document.
- Parse and split the document into chunks.
- Generate embeddings with a local
bge-m3model. - Store vectors in Qdrant.
- Embed the user's question.
- Retrieve the most relevant chunks.
- Generate an answer with a local LLM.
- Return the answer with source references.
The application does not call a cloud AI API at runtime.
Initial installation still requires network access to pull Docker images and Ollama models. After that, the stack can run inside a local or air-gapped environment.
Architecture
LocalRAG uses three main containers:
Browser UI
|
v
FastAPI Backend
- document parsing
- chunking
- RAG orchestration
- REST API and static frontend
|
+--> Qdrant
| vector storage and search
|
+--> Ollama
bge-m3 embeddings
local LLM generation
Why these components?
| Layer | Choice | Reason |
|---|---|---|
| Backend | FastAPI | Easy to inspect, modify, and deploy |
| Vector database | Qdrant | One container, clean API, persistent storage |
| Embedding runtime | Ollama + bge-m3 | Local, multilingual, avoids a separate Python model stack |
| Generation runtime | Ollama + Qwen2.5 | Easy model switching from 1.5B to larger models |
| Frontend | Plain HTML/CSS/JS | No frontend build step |
| Deployment | Docker Compose | Reproducible and easy to run locally |
I deliberately did not build a new model runtime, vector database, or agent engine.
The interesting engineering problem is not reinventing Ollama. It is making the complete workflow reproducible, observable, and usable in a private deployment.
Verified test
I tested the current release with:
- Docker Desktop 4.95.0
- Ollama 0.11.3
- Qdrant
-
bge-m3for embeddings -
qwen2.5:3bfor generation
The test document was a fictional employee handbook.
The system:
- Indexed it into three chunks.
- Received the question: “How many days of annual leave do full-time employees receive?”
- Retrieved the relevant policy text.
- Returned “15 days.”
- Displayed the source document and similarity score in the UI.
A health check returns:
{
"status": "ok",
"qdrant": "ok",
"ollama": "ok",
"llm_model": "qwen2.5:3b",
"embed_model": "bge-m3"
}
The repository includes screenshots and a 42-second demo video.
Running it
git clone https://github.com/Pingredsai/local-rag.git
cd local-rag
cp .env.example .env
docker compose up -d
docker exec localrag-ollama ollama pull qwen2.5:3b
docker exec localrag-ollama ollama pull bge-m3
Open:
http://localhost:8080
Four problems I ran into
1. The official Ollama image was too large
The latest Docker image is convenient, but the unpacked image can approach 9 GB. In a slow network environment, that is a serious deployment problem.
I pinned the project to a smaller compatible Ollama release and documented the deployment tradeoff.
This is a reminder that runtime size and download behavior are part of the product experience, not just infrastructure details.
2. A slow Docker registry is not the same as a broken Docker installation
Docker Hub was extremely slow in my environment. The solution was a combination of a mirror, resumable pulls, and local model import in some cases.
I did not hard-code a third-party mirror into the project. Instead, the deployment notes describe the approach so operators can replace it.
3. The original streaming response did not show sources
The first implementation returned text from the streaming endpoint, while sources were only available from the non-streaming endpoint.
That made the UI look like a normal chatbot. The source references, which are essential in a business system, were invisible.
I changed the streaming endpoint to return source metadata in a response header. The frontend reads the header, streams the answer, and renders the source cards when the answer completes.
4. A 1.5B model was not reliable enough for the demo
The smaller model worked on short context but produced inconsistent responses on slightly longer documents.
I switched the demo to qwen2.5:3b and tightened the prompt and generation options.
Local AI is not automatically better just because it is smaller. Model size must match the hardware, use case, and required answer quality.
Current limitations
The first version is intentionally narrow.
It does not include:
- OCR for scanned PDFs
- Multi-tenant permissions
- Multiple isolated knowledge bases
- Audit logging
- A reranker
- Visual agent orchestration
The goal of v0.1.0 is to make the core path dependable:
upload -> index -> retrieve -> answer -> show sources
I would rather state those boundaries clearly than imply that the project is already a production system.
What I plan to improve next
The next work is focused on the existing engineering path:
- Improve PDF and DOCX parsing
- Evaluate and refine chunking strategies
- Improve retrieval quality and testing
- Polish streaming answers and source rendering
- Add clearer checks for ports, models, and data directories
- Document backup, restore, and upgrade paths
- Continue reducing deployment friction
Before adding more modules, I want the core path to be dependable:
upload -> index -> retrieve -> answer -> show sources
Feedback and contributions
LocalRAG is an open-source project under active development.
If you work with local inference, RAG, or private deployment, feedback is welcome through GitHub issues and pull requests.
Repository:
https://github.com/Pingredsai/local-rag
The most useful reports are reproducible bugs, deployment issues, retrieval quality observations, and documentation improvements.
Top comments (2)
the part i keep hitting on my own rig is the one you put in problem one. the official ollama image unpacks close to nine gigabytes, and on a slow line that is the whole deployment, not the model. i pin a smaller release and document the tradeoff exactly the way you did, because the download behaviour is part of the product, not just the infra.
the streaming-sources fix is the detail that separates a demo from something a team would trust. a rag answer with no visible source reads like a chatbot, and the moment you put the document and the similarity score in the header, it stops being a guess. i run a small monitor that watches the ollama and qdrant containers per process (github.com/SikamikanikoBG/homelab-monitor) — the health check you show is the right shape, and the one thing i add is the vram on the embedding model, because bge-m3 is the quiet one that eats memory when the context grows.
Thanks — this is a very useful framing.
You're right that download behavior is part of the deployment experience, not just an infrastructure concern. I pinned a smaller Ollama release for the same reason and documented the tradeoff.
The source-header fix was the point where the UI stopped feeling like a generic chatbot. Once the document and similarity score are visible, the answer becomes verifiable instead of just plausible.
The per-process monitor is a good idea. Tracking embedding VRAM is especially relevant for bge-m3, since context growth is where it can quietly become expensive. I'll take a look at homelab-monitor and consider adding a diagnostic check around this.