Ich habe mir ein eigenes RAG gebaut. Hier ist, wie das funktioniert.

Ich habe ein selbst gehostetes RAG-System mit Qdrant, FastAPI und MCP-Integration gebaut. Hier ist der komplette Stack, alle Bugs und was ich dabei gelernt habe.

Irgendwann nervt es. Du fragst die KI nach deinen eigenen Artikeln, deinen eigenen Notizen, deinem eigenen Wissen, und sie antwortet dir mit generischem Trainings-Content aus 2023. Gut gemeint. Aber nicht das, was du gefragt hast.

Mein Lösungsansatz: ein selbst gehostetes RAG-System, das meinen gesamten Blog kennt, als MCP-Server läuft und direkt in Claude integriert ist. Kein SaaS-Abo, keine Datenweitergabe an Dritte, keine monatliche Rechnung für etwas, das ich selbst bauen kann.

Das hier ist kein Theo­rie-Artikel. Das ist der Bauplan für das System, das ich gerade produktiv betreibe. Mit allen Bugs, die dabei aufgetaucht sind.

Was ist RAG überhaupt, und warum sollte dich das interessieren?

RAG steht für Retrieval-Augmented Generation. Der Grundgedanke ist einfach: Du gibst einem Sprachmodell nicht nur einen Prompt, sondern vorher noch relevante Dokumente aus deiner eigenen Wissensbasis. Das Modell antwortet dann auf Basis dieser Dokumente, nicht auf Basis seines Trainings.

Ich betreibe digitalhandwerk.rocks seit Jahren, mittlerweile über 500 Artikel. Ohne RAG weiß Claude davon nichts. Mit RAG kann ich fragen: „Was habe ich über den EU AI Act geschrieben?“ und bekomme eine Antwort, die auf meinen eigenen Texten basiert. Das ist der Unterschied.

Für Vibe Coder ist das ein interessantes Projekt, weil es mehrere Technologien kombiniert, die einzeln einfach zu bedienen sind, zusammen aber etwas Sinnvolles ergeben: Vector-Datenbank, Embeddings, FastAPI, Docker und am Ende ein MCP-Server, den du direkt in Claude einbinden kannst.

Der Stack

Bevor ich die Details erkläre: Hier ist, was ich einsetze.

Qdrant als Vector-Datenbank. Qdrant ist in Rust geschrieben, performt gut, hat eine saubere REST-API und läuft problemlos als Docker-Container. Version 1.17.1 war meine Wahl, der Grund dafür kommt gleich.

FastAPI als API-Layer. Python 3.12, deployed aus einem GitHub-Repo via Coolify. Der Vorteil von FastAPI: du kriegst automatisch eine OpenAPI-Dokumentation, und der Code liest sich fast wie Pseudocode.

Google Gemini gemini-embedding-001 für die Embeddings. Warum nicht ein lokales Embedding-Modell? Weil ich die Rechenleistung auf meinem Hetzner-Server nicht verschwenden wollte. Gemini-Embeddings sind gut, schnell und für diesen Use Case ausreichend.

Coolify auf Hetzner als Infrastruktur. Das ist mein Standard für alles, was Docker braucht. Coolify ist ein selbst gehostetes Deployment-Tool, das Traefik als Reverse Proxy integriert und Let’s Encrypt-Zertifikate automatisch verwaltet.

Wie das System aufgebaut ist

Der Ablauf ist folgender:

Ein WordPress-Crawler läuft täglich um 6:00 Uhr als Cron-Job. Er holt alle Artikel von digitalhandwerk.rocks, zerlegt sie in Chunks, berechnet via Gemini ein Embedding für jeden Chunk und speichert Vektor plus Metadaten in Qdrant. Aktuell sind das 1.344 Chunks.

Wenn ich eine Suchanfrage stelle, passiert dasselbe in die andere Richtung: Die Anfrage wird in einen Vektor umgewandelt, Qdrant sucht die semantisch ähnlichsten Chunks, und diese werden als Kontext an Claude übergeben.

Das Ganze läuft als MCP-Server und ist in claude.ai als Custom Connector eingetragen. Jede Claude-Session hat damit Zugriff auf meine gesamte Wissensbasis.

Die Bugs, die mich eine halbe Nacht gekostet haben

Ich wäre nicht ehrlich, wenn ich hier nur den fertigen Stand zeigen würde. Der Weg dorthin hatte Stolperstellen.

Bug 1: LlamaIndex Embedding-Wrapper funktioniert nicht.

Ich hatte ursprünglich LlamaIndex 0.2.1 als Wrapper für die Gemini-Embeddings verwendet. Klingt praktisch, weil LlamaIndex viel Infrastruktur mitbringt. In der Praxis: der Wrapper in dieser Version ist fehlerhaft. Die Embeddings kamen mit falscher Dimension zurück, und Qdrant hat sie abgelehnt.

Fix: LlamaIndex komplett raus, direkter Aufruf über das google-genai SDK. Weniger Magie, weniger Fehlerquellen, gleiche Funktionalität.

Bug 2: Qdrant v1.18 hat search() zu query_points() umbenannt.

Das ist der Grund, warum ich bei v1.17.1 geblieben bin. In Version 1.18 wurde die primäre Suchmethode umbenannt, ohne dass das in meiner damaligen Dokumentation stand. Der Code hat still versagt, keine Exception, einfach leere Ergebnisse.

Wenn du dieses System nachbaust und eine aktuelle Qdrant-Version verwendest: ersetze alle search()-Aufrufe durch query_points(). Oder pin dich wie ich auf 1.17.1.

Bug 3: Qdrant-Hostname nicht auflösbar im Coolify-Netzwerk.

Coolify verwaltet interne Docker-Netzwerke, und normalerweise können Container innerhalb desselben Stacks über den Servicenamen miteinander kommunizieren. In meiner Konfiguration hat das nicht funktioniert. Der FastAPI-Container konnte qdrant als Hostname nicht auflösen.

Fix: direkte interne IP-Adresse 10.0.1.11 statt Hostname. Nicht elegant, aber stabil. Wer das sauberer lösen will, kann das Coolify-Netzwerk explizit konfigurieren und einen gemeinsamen Docker-Stack definieren.

Bug 4: Cron-Job mit externer URL schlägt fehl.

Der Crawler wurde initial so konfiguriert, dass er die öffentliche URL aufruft. Macht auf den ersten Blick Sinn. In der Praxis: der Container ruft sich selbst über Traefik auf, was zu Timeout-Problemen geführt hat.

Fix: intern auf localhost:8000 umgestellt. Der Container spricht direkt mit sich selbst, kein Umweg über den Reverse Proxy.

Duplikat-Prevention: Der Teil, den die meisten vergessen

Wenn ein Artikel aktualisiert wird und der Crawler ihn erneut indexiert, willst du keine doppelten Chunks in der Datenbank. Qdrant hat dafür keine eingebaute Upsert-Logik auf Basis von Metadaten.

Meine Lösung: vor jedem Ingest wird delete_by_filter via FilterSelector aufgerufen. Der Filter matcht auf die Source-URL des Dokuments. Alle alten Chunks dieser URL werden gelöscht, bevor die neuen reingeschrieben werden.

Das klingt brute-force, ist es auch. Aber es funktioniert zuverlässig, und für einen Blog mit einem täglichen Update-Zyklus ist das ausreichend.

Die Web-UI

Ich habe eine einfache Web-UI direkt in die FastAPI-App integriert, unter der Root-Route /. Sie zeigt:

  • Login-Screen (Basic Auth)
  • Stats: Anzahl Chunks, Anzahl indexierter Quellen
  • Ingest-Formulare für URLs, Dateien, Text und WordPress-Feeds
  • Suchfunktion mit Anzeige der Top-Ergebnisse

Das ist kein Design-Wettbewerb. Es ist ein Admin-Interface für mich, und es tut, was es soll. Wer die API direkt anspricht, braucht sie nicht.

MCP-Integration: Wo das Ganze interessant wird

Das RAG-System alleine wäre schon nützlich. Aber die eigentliche Stärke kommt durch die MCP-Integration.

Ich habe zwei MCP-Server gebaut:

Lokaler Node.js MCP-Server für Claude Desktop auf Windows und Mac. Der liegt lokal auf dem Gerät und kommuniziert mit dem Remote-RAG über HTTPS.

Remote MCP-Server deployed auf Hetzner via Coolify. Dieser nutzt StreamableHTTPServerTransport, was der aktuelle Standard für Remote-MCP-Server ist.

Den Remote-Server kann ich in claude.ai als Custom Connector eintragen. Einmal eingetragen, ist er account-weit verfügbar. Auf jedem Gerät, auf dem ich mit meinem Account eingeloggt bin, hat Claude Zugriff auf meine Wissensbasis. Mac mini, MacBook Air, iPhone, alles.

Ein Fallstrick beim Remote-MCP auf Coolify: Traefik hatte standardmäßig Gzip-Komprimierung aktiviert. Das hat den Streaming-Transport gebrochen, weil die Chunks komprimiert und in falscher Reihenfolge ankamen. Fix: traefik.http.middlewares.gzip.compress=true deaktivieren.

Was das System kann, und was nicht

Ich sage das nicht, um euch zu beeindrucken. Ich sage es, weil ich nach einigen Tagen Betrieb ein realistisches Bild habe.

Was gut funktioniert: Semantische Suche über meinen Blog-Content. Wenn ich wissen will, was ich über ein Thema geschrieben habe, ist das System zuverlässig. Auch Fragen wie „Was sind die wichtigsten Argumente, die ich gegen [Thema X] angebracht habe?“ werden korrekt beantwortet.

Was schwieriger ist: Sehr spezifische Fragen, die genaue Zitate erfordern. Das Chunking zerteilt Absätze manchmal an ungünstigen Stellen. Wer das optimieren will, sollte mit Chunk-Overlap experimentieren und semantisches Chunking in Betracht ziehen.

Was ich noch nicht gemacht habe: Hybridsuche, also die Kombination von Vektor-Ähnlichkeit und klassischem Keyword-Matching. Qdrant unterstützt das, aber für meinen aktuellen Use Case war es nicht notwendig.

Meine Beobachtung nach mehreren Tagen Betrieb

Das war kein Wochenendprojekt. Die eigentliche Architektur steht schnell. Die Bugs kosten Zeit, weil sie selten dort auftauchen, wo du sie erwartest.

Wer das nachbauen will: der komplette Stack ist öffentlich dokumentiert, die Repos sind privat auf GitHub. Ich habe bewusst auf eine fertige RAG-Plattform verzichtet, weil ich verstehen wollte, was wirklich passiert. Das hat sich gelohnt.

Wenn du ein ähnliches System planst und Fragen hast, bin ich erreichbar. Ich beantworte keine Anfragen für „bitte check kurz meinen Code“, aber für inhaltliche Fragen zur Architektur gerne.

Du kannst warten, bis es ein Plug-and-Play-Tool gibt, das alles für dich macht. Oder du baust es jetzt selbst und weißt nach dem ersten Cron-Bug mehr über Vector-Datenbanken als nach drei Tutorials.

share that. please.

LinkedIn
Facebook
Threads
Telegram
WhatsApp
Email
Alex Januschewsky, KI-Berater und Prompt Rocker
Über den Autor
Alex Januschewsky
KI-Berater & Prompt Rocker // Salzburg, AT

Ich arbeite seit 1989 in Kommunikation und Werbung, heute als KI-Berater unter der Marke Prompt Rocker. Ich baue Dinge selbst: mit Claude Code, n8n, WordPress und Coolify auf Hetzner. Vibe Coding ist für mich kein Begriff, sondern tägliche Praxis. Auf vibecraft.rocks dokumentiere ich, was dabei entsteht.

ship safe.