Zum Inhalt springen
← Alle Beiträge
· 4 Min. Lesezeit·Emre Yurtbay

Qdrant self-hosten: Vektordatenbank für RAG per Docker in 15 Minuten

Ein lauffähiges Minimalbeispiel – Qdrant per Docker mit API-Key und persistentem Volume starten, Collection anlegen, Vektoren upserten, Similarity-Query per curl – plus Gridstore-Upgrade-Falle.

QdrantVektordatenbankRAGEmbeddingsSelf-HostingDockerDevOpsKI

Wer ein LLM auf eigenen Firmendaten antworten lassen will, kommt an Retrieval-Augmented Generation (RAG) nicht vorbei: Statt das Modell zu feintunen, sucht man zur Anfrage die passenden Dokumente heraus und reicht sie als Kontext mit. Das Herzstück dieser Suche ist eine Vektordatenbank. Qdrant ist dafür eine der beliebtesten Open-Source-Optionen und in wenigen Minuten self-gehostet. Dieser Beitrag setzt einen minimalen, aber vollständigen Qdrant-Dienst auf – abgesichert per API-Key, mit persistentem Storage – und geht die Kernidee einmal end-to-end durch: Collection anlegen, Vektoren upserten, Ähnlichkeitssuche absetzen. Er ergänzt den bereits veröffentlichten LLM-Serving-Beitrag um den fehlenden Retrieval-Layer.

Was Qdrant macht – und die Kernidee

Ein Embedding-Modell wandelt Text (oder Bilder, Code, …) in einen Vektor aus Zahlen um. Semantisch ähnliche Inhalte landen dabei nah beieinander im Vektorraum. Qdrant speichert diese Vektoren samt Metadaten (dem Payload) und beantwortet die Frage „welche gespeicherten Vektoren liegen einem Anfrage-Vektor am nächsten?" – schnell, auch bei Millionen Einträgen.

Die eine Entscheidung, die Sie verstehen müssen: die Distanzmetrik. Sie muss zum Embedding-Modell passen. Modelle wie OpenAIs text-embedding-3 oder viele Sentence-Transformer sind auf Cosine ausgelegt; andere erwarten Dot oder Euclid. Wählt man hier falsch, sind die Suchergebnisse schlicht schlecht – ohne dass etwas abstürzt. Die zweite Entscheidung ist die Vektor-Dimension: Sie ist durch das Modell fest vorgegeben (z. B. 1536 bei text-embedding-3-small).

Qdrant per Docker starten

Eine compose.yaml genügt. Wir setzen sofort einen API-Key – dazu gleich mehr bei den Stolperfallen – und mounten ein Volume, damit die Daten einen Neustart überleben:

services:
  qdrant:
    image: qdrant/qdrant:v1.18.2
    ports:
      - "6333:6333"
      - "6334:6334"
    environment:
      QDRANT__SERVICE__API_KEY: <YOUR_API_KEY>
    volumes:
      - qdrant_storage:/qdrant/storage
    restart: unless-stopped

volumes:
  qdrant_storage:

Starten und prüfen:

docker compose up -d
curl -s http://localhost:6333/healthz

Das Web-Dashboard liegt unter http://localhost:6333/dashboard. Ab jetzt braucht jeder Request den Header api-key. Wir legen den Schlüssel einmal als Shell-Variable ab:

export QDRANT_KEY=<YOUR_API_KEY>

Das Minimalbeispiel: Collection, Upsert, Query

Zur Veranschaulichung nehmen wir winzige 4-dimensionale Vektoren von Hand – in der Praxis liefert sie Ihr Embedding-Modell. Zuerst die Collection mit Dimension und Metrik anlegen:

curl -s -X PUT http://localhost:6333/collections/docs \
  -H "api-key: $QDRANT_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "vectors": { "size": 4, "distance": "Cosine" }
  }'

Jetzt ein paar Punkte upserten. Jeder Punkt hat eine id, den vector und ein frei wählbares payload (hier der Originaltext, den Sie später ans LLM reichen):

curl -s -X PUT http://localhost:6333/collections/docs/points \
  -H "api-key: $QDRANT_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "points": [
      { "id": 1, "vector": [0.10, 0.20, 0.30, 0.90],
        "payload": { "text": "Rechnung stornieren" } },
      { "id": 2, "vector": [0.90, 0.10, 0.10, 0.20],
        "payload": { "text": "Passwort zuruecksetzen" } },
      { "id": 3, "vector": [0.15, 0.25, 0.35, 0.85],
        "payload": { "text": "Gutschrift beantragen" } }
    ]
  }'

Und schließlich die Ähnlichkeitssuche: Zu einem Anfrage-Vektor die drei nächsten Treffer, inklusive Payload:

curl -s -X POST http://localhost:6333/collections/docs/points/query \
  -H "api-key: $QDRANT_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "query": [0.12, 0.22, 0.32, 0.88],
    "limit": 3,
    "with_payload": true
  }'

Qdrant liefert die Punkte nach score sortiert zurück. Der Anfrage-Vektor liegt nah bei den Punkten 1 und 3 – genau die tauchen oben auf, während Punkt 2 zurückfällt. Das ist der gesamte RAG-Retrieval-Schritt in einem Aufruf: Die zurückgegebenen text-Payloads würden Sie nun als Kontext an Ihr LLM reichen.

Der Datenfluss in der Übersicht:

   +-----------+   embedding   +----------------+   upsert    +------------+
   |  Dokument | ------------> |  Vektor + text | ----------> |   Qdrant   |
   +-----------+               +----------------+             | collection |
                                                              |   "docs"   |
   +-----------+   embedding   +----------------+   query     +-----+------+
   |   Frage   | ------------> |  Anfrage-Vektor| ----------------> |
   +-----------+               +----------------+   top-k      +----v-------+
                                                               | Treffer +  |
                                                               |  payload   |
                                                               +------------+

Drei typische Stolperfallen

1. Kein API-Key = offener Vollzugriff. Startet Qdrant ohne QDRANT__SERVICE__API_KEY, läuft es komplett ohne Authentifizierung – jeder mit Netzwerkzugriff kann lesen, schreiben und Collections löschen. Setzen Sie den Key immer (das Env-Format ist Präfix QDRANT__, geschachtelte Keys durch doppelte Unterstriche getrennt) und exponieren Sie Port 6333 nie ungeschützt ins Internet. Für Produktion gehört TLS davor, sonst geht der Key im Klartext über die Leitung.

2. Port 6333 (REST) vs. 6334 (gRPC). Qdrant bietet zwei Schnittstellen: REST/HTTP auf 6333 und gRPC auf 6334. Die curl-Beispiele oben nutzen REST. Wenn ein Client-SDK „connection refused" meldet, ist meist der gRPC-Port 6334 nicht gemappt – die offiziellen Clients sprechen oft gRPC. Mappen Sie im Zweifel beide.

3. Die Upgrade-Landmine v1.15 → v1.17. Mit v1.17.0 wurde die alte Storage-Engine RocksDB vollständig durch Gridstore ersetzt. Ein direkter Sprung von v1.15.x auf v1.17.x (oder das aktuelle v1.18.2) schlägt mit einem unsupported storage-Fehler fehl. Sie müssen eine Minor-Version pro Schritt upgraden: erst v1.15 → v1.16, den Dienst einmal sauber starten und die Migration durchlaufen lassen, dann v1.16 → v1.17, und so weiter. Fixieren Sie im Compose-File deshalb einen konkreten Tag statt :latest, damit ein docker compose pull Sie nicht versehentlich über diese Grenze hebt.

Wie es weitergeht

Damit steht das Fundament: Ein Qdrant-Dienst, abgesichert und persistent, der Vektoren aufnimmt und Ähnlichkeitssuchen beantwortet. Die naheliegenden nächsten Schritte sind ein echtes Embedding-Modell statt Handvektoren, gefilterte Suchen über Payload-Felder und einer der offiziellen Clients statt curl. Tiefer geht es im Qdrant-Quickstart, im Security-Guide und in den Release-Notes zu v1.17.0, bevor Sie ein Upgrade angehen.

Hinweis: Die Beiträge dieses Blogs werden unter Einsatz von KI erstellt und vor der Veröffentlichung redaktionell geprüft. Die redaktionelle Verantwortung trägt Emre Yurtbay (siehe Impressum).

Projekt besprechen