Qdrant in Docker: a first vector search
This guide takes you from nothing to a working vector similarity search in about ten minutes. You run Qdrant in a Docker container, create a collection, insert a few points by hand, and search them with plain curl. No Python environment, no embedding model, no cleanup anxiety: everything lives in one container and one named volume, and the last section removes both.
Hand-written four-dimensional vectors stand in for real embeddings so every command here is copy-paste-safe. The concepts transfer unchanged when a model produces the vectors instead of you.
Prerequisites
-
Docker Engine or Docker Desktop. Verify with:
docker --versionYou should see a version string, not "command not found." Any recent version works.
-
curl, which ships with macOS, Linux, and Windows 10+.
-
Ports 6333 and 6334 free on your machine. If another service owns them, adjust the
-pflags below and every URL to match.
Run Qdrant
Start the container with a named volume so your data survives restarts:
docker run -d --name qdrant \
-p 6333:6333 -p 6334:6334 \
-v qdrant_storage:/qdrant/storage \
qdrant/qdrant
Port 6333 serves the REST API and dashboard; 6334 serves gRPC, which this guide does not use.
Checkpoint. Ask the server to identify itself:
curl http://localhost:6333/
You should see a small JSON object with a "title" naming qdrant and a "version". If you get connection refused, run docker ps to confirm the container is up, and docker logs qdrant to see why if it is not.
Qdrant also ships a browser dashboard at http://localhost:6333/dashboard, worth opening now so you can watch the next steps land.
Create a collection
A collection is a named set of points that share a vector size and a distance metric. Create one for four-dimensional vectors compared by cosine similarity:
curl -X PUT http://localhost:6333/collections/first_steps \
-H "Content-Type: application/json" \
-d '{ "vectors": { "size": 4, "distance": "Cosine" } }'
Checkpoint. The response should be:
{"result":true,"status":"ok","time":0.1}
(The time value differs run to run. Rerunning the command returns an error that the collection already exists, which is itself a good sign.)
Insert points
A point is an id, a vector, and an optional payload of plain JSON. These four points describe documents, with a kind payload to filter on later. The ?wait=true flag makes the call block until the write is durable, so the checkpoint below is honest:
curl -X PUT "http://localhost:6333/collections/first_steps/points?wait=true" \
-H "Content-Type: application/json" \
-d '{
"points": [
{ "id": 1, "vector": [0.90, 0.10, 0.05, 0.05],
"payload": { "title": "Backup and restore runbook", "kind": "runbook" } },
{ "id": 2, "vector": [0.85, 0.15, 0.10, 0.05],
"payload": { "title": "Disaster recovery runbook", "kind": "runbook" } },
{ "id": 3, "vector": [0.10, 0.90, 0.10, 0.05],
"payload": { "title": "Balance calibration SOP", "kind": "sop" } },
{ "id": 4, "vector": [0.05, 0.10, 0.90, 0.10],
"payload": { "title": "Data API integration notes", "kind": "notes" } }
]
}'
Checkpoint. The response reports "status": "completed". Confirm the count landed:
curl http://localhost:6333/collections/first_steps
Look for "points_count": 4 in the result.
Search
The two runbook vectors you just inserted point in nearly the same direction; the SOP and the notes point elsewhere. Search with a query vector close to the runbooks:
curl -X POST http://localhost:6333/collections/first_steps/points/search \
-H "Content-Type: application/json" \
-d '{
"vector": [0.88, 0.12, 0.05, 0.06],
"limit": 2,
"with_payload": true
}'
Checkpoint. The top two results should be points 1 and 2, the runbooks, each with a score above 0.99. Cosine scores in Qdrant run up to 1.0, where 1.0 means "same direction." That is vector search doing its one job: nearest meaning first.
Search with a filter
Payloads make the search conditional. Ask for things near the query vector, but only where kind is sop:
curl -X POST http://localhost:6333/collections/first_steps/points/search \
-H "Content-Type: application/json" \
-d '{
"vector": [0.88, 0.12, 0.05, 0.06],
"limit": 2,
"with_payload": true,
"filter": {
"must": [ { "key": "kind", "match": { "value": "sop" } } ]
}
}'
Checkpoint. Exactly one result comes back: point 3, the calibration SOP, despite its lower similarity score. Filters constrain the candidate set; the vector still ranks whatever survives.
Clean up
docker stop qdrant && docker rm qdrant
docker volume rm qdrant_storage
Checkpoint. docker ps -a no longer lists the container, and docker volume ls no longer lists qdrant_storage.
Troubleshooting
| Symptom | Likely cause | Fix |
|---|---|---|
port is already allocated | Another service owns 6333 or 6334 | Change the left side of the -p mappings, and every URL in this guide, to a free port |
The container name "/qdrant" is already in use | A previous run left a container behind | docker rm -f qdrant, then rerun the docker run command |
connection refused on every curl | Container not running, or Docker daemon stopped | docker ps, then docker logs qdrant |
Search returns fewer results than limit | Small collections are small | Expected with 4 points; limit is a maximum, not a promise |
Where to go next
- Replace the hand-written vectors with real embeddings from a sentence-transformer model, keeping every other call identical (only
sizechanges). - Move from raw REST to the official Python or JavaScript client libraries.
- Read the Qdrant documentation for quantization, snapshots, and running beyond a single node.