Qdrant Vector Database with Docker: Install, Test and Secure It for Local RAG
Before you start
- 10 min read
- 27 to run in terminal
- 4 file contents
Boxes marked Run in terminal are what you type; plain text explains why. Check what you need first

What you get in one minute#
Qdrant is an open-source vector search engine that stores vectors and their payload, then returns the nearest matches for a query vector.
For a local RAG setup, you use it as the memory layer: your embedding model turns text into numbers, Qdrant stores those numbers next to the original text (the payload), and your app asks for the closest matches at question time. It runs as a single Docker container with no cloud account, so your documents stay on your machine. The whole walkthrough below takes a few minutes and you end with a tested, secured instance.
| What | Measured / given |
|---|---|
| Time needed | About 5-10 minutes, including the first image pull |
| RAM measured | 83.28 MiB idle with 3 points (docker stats) |
| Image size | 72,596,695 bytes (~73 MB) |
| Ports | 6333 REST API + dashboard, 6334 gRPC |
How it fits together#
Your app sends a query vector over the REST API on port 6333, Qdrant compares it against the stored vectors and returns the closest points with their payload.
You only need port 6333 for the steps here. Port 6334 is the gRPC port for client libraries that prefer it.
Before you start#
You need Docker running and either a terminal (Linux/Mac) or PowerShell 7 (Windows).
| System | Requirements | Test status |
|---|---|---|
| Linux | Docker Engine (or Docker Desktop), bash, curl | Tested |
| Windows | Docker Desktop, PowerShell 7 | Tested |
| Mac | Docker Desktop for Mac, Terminal (zsh or bash), curl | Not tested; commands are the same as Linux |
Tested on 2026-10-25: Linux = Ubuntu 24.04 (WSL2) with bash and curl 8.5.0, Docker 29.7.2; Windows 11 with Docker Desktop 29.7.2 and PowerShell 7; Mac not tested.
Set the base address once so every later command stays short. This also makes it easy to point at another machine later.
Linux#
export QDRANT_URL=http://localhost:6333
Windows (PowerShell)#
$env:QDRANT_URL = 'http://localhost:6333'
Mac (Terminal)#
Same commands as Linux:
export QDRANT_URL=http://localhost:6333
Step 1: Pull the image and start Qdrant with a named volume#
Start Qdrant with a named Docker volume so your data survives container restarts.
A named volume keeps the data outside the container. The official docs show a bind mount with -v "$(pwd)/qdrant_storage:/qdrant/storage:z", but they also note that on Windows you may need a named Docker volume instead. A named volume works the same way on all three systems, so this guide uses one.
Linux#
docker pull qdrant/qdrant:latest docker run -d --name qdrant -p 6333:6333 -p 6334:6334 -v qdrant_storage:/qdrant/storage qdrant/qdrant:latest
Windows (PowerShell)#
docker pull qdrant/qdrant:latest docker run -d --name qdrant -p 6333:6333 -p 6334:6334 -v qdrant_storage:/qdrant/storage qdrant/qdrant:latest
Mac (Terminal)#
Same commands as Linux:
docker pull qdrant/qdrant:latest docker run -d --name qdrant -p 6333:6333 -p 6334:6334 -v qdrant_storage:/qdrant/storage qdrant/qdrant:latest
Step 2: Check that it runs#
Call the root URL and open the dashboard to confirm the container is healthy.
The root URL returns the title and version. The dashboard is a browser UI at http://localhost:6333/dashboard. In the test the dashboard answered with status 200.
Linux#
curl -s "$QDRANT_URL/"
curl -s -o /dev/null -w '%{http_code}\n' "$QDRANT_URL/dashboard/"
Windows (PowerShell)#
Invoke-RestMethod "$env:QDRANT_URL/" | ConvertTo-Json -Compress (Invoke-WebRequest "$env:QDRANT_URL/dashboard/").StatusCode
Mac (Terminal)#
Same commands as Linux:
curl -s "$QDRANT_URL/"
curl -s -o /dev/null -w '%{http_code}\n' "$QDRANT_URL/dashboard/"
The root URL answered like this in the test:
{"title":"qdrant - vector search engine","version":"1.20.0","commit":"..."}
Step 3: Create a collection#
Create a collection with vector size 4 and the Dot distance so the sample data fits.
A collection is the table of Qdrant. The vector size must match the embedding model you will use later. Size 4 and Dot are only for this test. The API answered with {"result":true,"status":"ok",...} in the test.
Linux#
curl -s -X PUT "$QDRANT_URL/collections/test_collection" \
-H 'Content-Type: application/json' \
-d '{"vectors":{"size":4,"distance":"Dot"}}'
Windows (PowerShell)#
Invoke-RestMethod -Method Put "$env:QDRANT_URL/collections/test_collection" `
-ContentType 'application/json' `
-Body '{"vectors":{"size":4,"distance":"Dot"}}' | ConvertTo-Json -Compress
Mac (Terminal)#
Same commands as Linux:
curl -s -X PUT "$QDRANT_URL/collections/test_collection" \
-H 'Content-Type: application/json' \
-d '{"vectors":{"size":4,"distance":"Dot"}}'
Step 4: Add three points with payload#
Add three points where each vector has four numbers and the payload holds the city name.
wait=true makes the request return only after the write is applied. The test answered with {"result":{"operation_id":1,"status":"completed"},"status":"ok",...}.
Linux#
curl -s -X PUT "$QDRANT_URL/collections/test_collection/points?wait=true" \
-H 'Content-Type: application/json' \
-d '{"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"}}
]}'
Windows (PowerShell)#
Invoke-RestMethod -Method Put "$env:QDRANT_URL/collections/test_collection/points?wait=true" `
-ContentType 'application/json' `
-Body '{"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"}}]}' | ConvertTo-Json -Compress
Mac (Terminal)#
Same commands as Linux:
curl -s -X PUT "$QDRANT_URL/collections/test_collection/points?wait=true" \
-H 'Content-Type: application/json' \
-d '{"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"}}
]}'
Step 5: Search for the nearest points#
Send a query vector and read the ranked result list.
The query [0.2,0.1,0.9,0.7] returned Berlin first, then Moscow, then London in the test. The limit caps how many points come back, and with_payload asks Qdrant to include the stored text.
Linux#
curl -s -X POST "$QDRANT_URL/collections/test_collection/points/query" \
-H 'Content-Type: application/json' \
-d '{"query":[0.2,0.1,0.9,0.7],"limit":3,"with_payload":true}'
Windows (PowerShell)#
Invoke-RestMethod -Method Post "$env:QDRANT_URL/collections/test_collection/points/query" `
-ContentType 'application/json' `
-Body '{"query":[0.2,0.1,0.9,0.7],"limit":3,"with_payload":true}' | ConvertTo-Json -Depth 6 -Compress
Mac (Terminal)#
Same commands as Linux:
curl -s -X POST "$QDRANT_URL/collections/test_collection/points/query" \
-H 'Content-Type: application/json' \
-d '{"query":[0.2,0.1,0.9,0.7],"limit":3,"with_payload":true}'
The test answered like this (values unchanged):
{"result":{"points":[{"id":1,"version":1,"score":1.273,"payload":{"city":"Berlin"}},{"id":3,"version":1,"score":1.208,"payload":{"city":"Moscow"}},{"id":2,"version":1,"score":0.871,"payload":{"city":"London"}}]},"status":"ok","time":0.001126189}
Step 6: Check that the data survives a restart#
Restart the container and read the point count to prove the named volume keeps your data.
The named volume holds the storage, so a restart does not wipe the collection. The test returned 3 points after the restart.
Linux#
docker restart qdrant curl -s "$QDRANT_URL/collections/test_collection" | grep -o '"points_count":[0-9]*'
Windows (PowerShell)#
docker restart qdrant (Invoke-RestMethod "$env:QDRANT_URL/collections/test_collection").result.points_count
Mac (Terminal)#
Same commands as Linux:
docker restart qdrant curl -s "$QDRANT_URL/collections/test_collection" | grep -o '"points_count":[0-9]*'
The point count came back as 3 in the test:
"points_count":3
Step 7: Lock it with an API key#
By default Qdrant starts with no encryption or authentication, so add an API key and send the api-key header from now on.
The key comes from the environment variable QDRANT__SERVICE__API_KEY. First make a random key with the command for your system, then put it in place of <YOUR_KEY> in the commands below. Keep the double quotes around QDRANT__SERVICE__API_KEY=... exactly as shown.
Linux#
openssl rand -hex 32
Then remove the open container and start a locked one:
docker rm -f qdrant docker run -d --name qdrant -p 6333:6333 -p 6334:6334 \ -e "QDRANT__SERVICE__API_KEY=<YOUR_KEY>" \ -v qdrant_storage:/qdrant/storage qdrant/qdrant:latest
Without the header you get 401; with it you get your collection list:
curl -s -o /dev/null -w '%{http_code}\n' "$QDRANT_URL/collections"
curl -s "$QDRANT_URL/collections" -H 'api-key: <YOUR_KEY>'
Windows (PowerShell)#
-join ((1..32) | ForEach-Object { '{0:x2}' -f (Get-Random -Maximum 256) })
Then remove the open container and start a locked one:
docker rm -f qdrant docker run -d --name qdrant -p 6333:6333 -p 6334:6334 ` -e "QDRANT__SERVICE__API_KEY=<YOUR_KEY>" ` -v qdrant_storage:/qdrant/storage qdrant/qdrant:latest
Without the header you get 401; with it you get your collection list:
try { Invoke-RestMethod "$env:QDRANT_URL/collections" } catch { $_.Exception.Response.StatusCode.value__ }
(Invoke-RestMethod "$env:QDRANT_URL/collections" -Headers @{ 'api-key' = '<YOUR_KEY>' }).result.collections.name
Mac (Terminal)#
Same commands as Linux:
openssl rand -hex 32
Then remove the open container and start a locked one:
docker rm -f qdrant docker run -d --name qdrant -p 6333:6333 -p 6334:6334 \ -e "QDRANT__SERVICE__API_KEY=<YOUR_KEY>" \ -v qdrant_storage:/qdrant/storage qdrant/qdrant:latest
Without the header you get 401; with it you get your collection list:
curl -s -o /dev/null -w '%{http_code}\n' "$QDRANT_URL/collections"
curl -s "$QDRANT_URL/collections" -H 'api-key: <YOUR_KEY>'
The locked instance answered like this in the test:
401
{"result":{"collections":[{"name":"test_collection"}]},"status":"ok","time":0.000022424}
Step 8: Stop and clean up#
Remove the container and the named volume when you are done, or keep the volume to resume later.
Removing the container alone keeps your data in the volume. Removing the volume deletes the stored points.
Linux#
docker rm -f qdrant docker volume rm qdrant_storage
Windows (PowerShell)#
docker rm -f qdrant docker volume rm qdrant_storage
Mac (Terminal)#
Same commands as Linux:
docker rm -f qdrant docker volume rm qdrant_storage
What the numbers mean#
The score tells you how close each stored vector is to your query vector, and higher is closer for the Dot distance used here.
With the query [0.2,0.1,0.9,0.7] the test ranked Berlin at 1.273, Moscow at 1.208 and London at 0.871. In plain words: Berlin is the best match, Moscow is very close behind, and London is clearly further away. The ranking is what matters, not the raw size of the number. Different distance settings produce different score ranges, so do not compare scores across collections. For RAG you usually take the top few points, put their payload text into the prompt and let the model answer from it.
Using it for RAG#
Real embeddings are 384 to 1536 or more numbers per text chunk, produced by an embedding model.
The size 4 vectors in this test are only for learning the API. When you move to real data, pick an embedding model, check its output dimension, and create the collection with that exact size. The vector size must match the model, or inserts fail. Qdrant stores the vectors plus the payload (the text, the source, an ID) and returns the nearest ones for a query vector. Your pipeline is then simple: embed chunks once and upsert them, embed the question at query time, search, and pass the returned payload text to your model. Keep the collection name and the model in one config so you never mix dimensions by accident.
Troubleshooting#
401 Unauthorized#
The API key is set on the server but missing or wrong in your request. Add the header api-key: <YOUR_KEY> to every call. Check that the container was started with QDRANT__SERVICE__API_KEY and that the quotes around the value are intact.
Port 6333 already in use#
Another process or an older Qdrant container holds the port. Stop the old container with docker rm -f qdrant, or publish a different host port such as -p 6335:6333 and set QDRANT_URL to that port. Only the host side changes; the container still listens on 6333.
Container not ready yet#
The first start can take a moment while storage initializes. If the first request fails or the connection is refused, wait a few seconds and try again. In the test Qdrant itself reported a search time of about 0.001-0.002 s, so a long wait usually means the container stopped. Check it with docker ps -a and read the logs with docker logs qdrant.
Windows folder mount issue#
If a bind mount to a Windows folder fails or the container cannot write, use a named volume instead: -v qdrant_storage:/qdrant/storage. The official docs note this case for Windows. The named volume used in this guide avoids the problem and works the same on Linux and Mac.
FAQ#
Is Qdrant free? Yes. Qdrant is open source under the Apache 2.0 license. You can run it locally with Docker at no cost.
Does it need a GPU? No. We ran it on CPU only; a search took about 0.002 s.
How much RAM does it use? 83.28 MiB idle with 3 points in the test (docker stats reported 83.28 MiB and 0.96% CPU). Real collections use more, so watch docker stats as you grow.
Is it secure by default? No. By default Qdrant starts with no encryption or authentication. Add an API key with QDRANT__SERVICE__API_KEY, send the api-key header, and keep port 6333 off the public internet.
Where is my data stored? In the Docker named volume qdrant_storage, mounted at /qdrant/storage in the container. Remove the volume and the data is gone.
Sources#
- Qdrant quickstart
- Qdrant security guide
- Docker Hub image page (read 2026-10-25)
- Tested by DIY Lab Hub on 2026-10-25 (Linux Ubuntu 24.04 on WSL2 with Docker 29.7.2 and curl 8.5.0; Windows 11 with Docker Desktop 29.7.2 and PowerShell 7; Mac not tested)