Setup guide · Tools and services
Qdrant vector database
Qdrant running locally in Docker with its dashboard open, and a collection created, filled and searched from TypeScript and Python.
Some retrieval exercises compare a dedicated vector database with pgvector, and Qdrant is the one they use. It stores points (a vector, an ID and a JSON payload) in collections, and searches them by similarity with payload filters. It runs as one container with a REST API, a gRPC API and a web dashboard.
Before you start
- Docker and Docker Compose installed and running.
- Ports 6333 and 6334 free.
- For the client code: Node.js, pnpm and TypeScript, Python with uv, or both.
1. Start Qdrant
Create a folder with this file:
services:
qdrant:
image: qdrant/qdrant
ports:
- "6333:6333"
- "6334:6334"
volumes:
- qdrant-data:/qdrant/storage
volumes:
qdrant-data:docker compose up -d- 6333 is the REST API and the dashboard. The TypeScript and Python clients in this page use it.
- 6334 is gRPC, used by the Rust, Go, Java and C# clients, and optionally by Python.
- A named volume, not a folder bind mount. Qdrant's quickstart mounts
./qdrant_storage, but its docs warn that bind mounts through Docker and WSL on Windows can lose data. A named volume avoids that on every OS.
Qdrant starts with no API key and no TLS, so anyone who can reach these ports can read and write. That's fine on your own machine; don't expose it on a public server without the security settings from Qdrant's docs.
QDRANT_URL=http://localhost:63332. Open the dashboard
Open http://localhost:6333/dashboard in your browser. It lists collections, shows points and their payloads, and has a console for sending REST requests. It's empty until the next step.
3. Create a collection from TypeScript
pnpm add @qdrant/js-client-restimport { QdrantClient } from "@qdrant/js-client-rest";
const client = new QdrantClient({ url: process.env.QDRANT_URL });
const name = "smoke_test";
if ((await client.collectionExists(name)).exists) await client.deleteCollection(name);
await client.createCollection(name, { vectors: { size: 4, distance: "Cosine" } });
await client.upsert(name, {
wait: true,
points: [
{ id: 1, vector: [0.05, 0.61, 0.76, 0.74], payload: { city: "Berlin" } },
{ id: 2, vector: [0.19, 0.81, 0.75, 0.11], payload: { city: "London" } },
{ id: 3, vector: [0.36, 0.55, 0.47, 0.94], payload: { city: "Moscow" } },
],
});
const result = await client.query(name, { query: [0.2, 0.1, 0.9, 0.7], limit: 2, with_payload: true });
console.log(result.points.map((p) => [p.payload?.city, p.score]));node --env-file=.env src/smoke.tssizemust equal your embedding model's output dimension (768 forembeddinggemma, for example). You can't change it later; you recreate the collection.distanceisCosine,Dot,EuclidorManhattan. Text embeddings usually useCosine.wait: truereturns only once the points are indexed, so the query right after sees them.
4. Create a collection from Python
uv add qdrant-clientimport os
from qdrant_client import QdrantClient
from qdrant_client.models import Distance, PointStruct, VectorParams
client = QdrantClient(url=os.environ["QDRANT_URL"])
name = "smoke_test"
if client.collection_exists(name):
client.delete_collection(name)
client.create_collection(name, vectors_config=VectorParams(size=4, distance=Distance.COSINE))
client.upsert(
collection_name=name,
wait=True,
points=[
PointStruct(id=1, vector=[0.05, 0.61, 0.76, 0.74], payload={"city": "Berlin"}),
PointStruct(id=2, vector=[0.19, 0.81, 0.75, 0.11], payload={"city": "London"}),
PointStruct(id=3, vector=[0.36, 0.55, 0.47, 0.94], payload={"city": "Moscow"}),
],
)
hits = client.query_points(name, query=[0.2, 0.1, 0.9, 0.7], limit=2, with_payload=True).points
print([(h.payload["city"], h.score) for h in hits])uv run --env-file .env python smoke.pyFor unit tests, QdrantClient(":memory:") runs an in-process version with the same API and no server. uv add "qdrant-client[fastembed]" adds FastEmbed, which computes embeddings locally on the CPU.
Refresh the dashboard: smoke_test is there with three points.
Check it works
curl -s http://localhost:6333/collectionsExpected output after either script (the time value varies):
{"result":{"collections":[{"name":"smoke_test"}]},"status":"ok","time":0.00001}Troubleshooting
ECONNREFUSED or ResponseHandlingException on port 6333. The container isn't running or is still starting. Run docker compose ps and docker compose logs qdrant. curl http://localhost:6333/healthz returns 200 once it's ready.
Wrong input: Vector dimension error: expected dim: 4, got 768. The collection's size doesn't match your embeddings. Delete the collection and create it with the model's dimension.
Bad Request on upsert with string IDs such as "doc-1". Point IDs must be unsigned integers or UUIDs. Hash your own IDs into a UUID, and keep the original in the payload.
The client warns that its version is incompatible with the server. Update the client (pnpm update @qdrant/js-client-rest or uv lock --upgrade-package qdrant-client) or pull the current image with docker compose pull && docker compose up -d.
Going further
- Docker and Docker Compose, for compose commands and cleaning up volumes
- Postgres with pgvector, vectors inside a relational database
- Local models with Ollama, for free embedding models to fill a collection
Sources
- Qdrant quickstartDocs · QdrantThe Docker command, ports 6333 and 6334, the dashboard, and creating, upserting and querying a collection.
- InstallationDocs · QdrantThe Docker Compose example, storage requirements and the warning about bind mounts on Windows.
- qdrant-jsDocs · QdrantInstalling `@qdrant/js-client-rest` and connecting by URL.
- qdrant-clientDocs · QdrantThe Python client, its FastEmbed extra and the in-memory local mode.