How to Install Qdrant Vector Database on VPS for AI & RAG
Qdrant is an open-source vector database written in Rust, purpose-built for storing and searching high-dimensional vector embeddings at scale. It excels at semantic search, recommendation engines, and especially RAG (Retrieval-Augmented Generation) — the technique that grounds LLM responses in your own private data.
This guide walks through installing Qdrant on a Linux VPS via Docker, creating collections, inserting and querying vectors via the REST API, integrating with the Python SDK, setting up Nginx TLS termination, and automating snapshot backups.
Why Self-Host Qdrant on Your VPS?
- Full data ownership — your embeddings never leave your server
- Significantly cheaper than managed services (Pinecone, Weaviate Cloud) at scale
- Low latency when your LLM inference (e.g., Ollama) and vector DB share the same VPS
- Fine-grained control over index parameters, HNSW tuning, and memory limits
1 System Requirements
| Resource | Minimum | Recommended |
|---|---|---|
| RAM | 2 GB | 8 GB or more |
| CPU | 2 vCPU | 4+ vCPU |
| Disk | 20 GB SSD | 50+ GB NVMe |
| OS | Ubuntu 22.04+ | Ubuntu 24.04 / Debian 12 |
2 Install Docker
curl -fsSL https://get.docker.com | sh
sudo usermod -aG docker $USER
newgrp docker
docker --version
3 Deploy Qdrant with Docker Compose
mkdir -p ~/qdrant/{config,storage,snapshots}
cd ~/qdrant
Create docker-compose.yml:
version: "3.8"
services:
qdrant:
image: qdrant/qdrant:latest
container_name: qdrant
restart: unless-stopped
ports:
- "127.0.0.1:6333:6333" # REST API (localhost only)
- "127.0.0.1:6334:6334" # gRPC
volumes:
- ./storage:/qdrant/storage
- ./snapshots:/qdrant/snapshots
- ./config/production.yaml:/qdrant/config/production.yaml:ro
environment:
- QDRANT__SERVICE__API_KEY=YOUR_SECRET_API_KEY_HERE
Create config/production.yaml:
storage:
optimizers_config:
default_segment_number: 2
service:
max_request_size_mb: 32
max_workers: 0 # 0 = use all CPU cores
telemetry_disabled: true
docker compose up -d
docker compose logs -f qdrant
4 Verify the API
# Check version
curl http://localhost:6333/
# Create a collection — 1536 dimensions (OpenAI ada-002)
curl -X PUT http://localhost:6333/collections/my_docs \
-H "Content-Type: application/json" \
-H "api-key: YOUR_SECRET_API_KEY_HERE" \
-d '{
"vectors": {
"size": 1536,
"distance": "Cosine"
}
}'
Cosine for text embeddings, Dot for pre-normalized vectors, Euclid for image or sensor data.5 Insert and Search Vectors
# Small 4-dim collection for testing
curl -X PUT http://localhost:6333/collections/test \
-H "Content-Type: application/json" \
-H "api-key: YOUR_SECRET_API_KEY_HERE" \
-d '{"vectors": {"size": 4, "distance": "Dot"}}'
# Upsert points
curl -X PUT http://localhost:6333/collections/test/points \
-H "Content-Type: application/json" \
-H "api-key: YOUR_SECRET_API_KEY_HERE" \
-d '{
"points": [
{"id": 1, "vector": [0.05, 0.61, 0.76, 0.74], "payload": {"city": "Bangkok"}},
{"id": 2, "vector": [0.19, 0.81, 0.75, 0.11], "payload": {"city": "Tokyo"}}
]
}'
# Nearest-neighbour search
curl -X POST http://localhost:6333/collections/test/points/search \
-H "Content-Type: application/json" \
-H "api-key: YOUR_SECRET_API_KEY_HERE" \
-d '{"vector": [0.2, 0.1, 0.9, 0.7], "limit": 2, "with_payload": true}'
6 Python SDK Integration
pip install qdrant-client openai
from qdrant_client import QdrantClient
from qdrant_client.models import Distance, VectorParams, PointStruct
import openai
client = QdrantClient(url="http://localhost:6333", api_key="YOUR_SECRET_API_KEY_HERE")
openai.api_key = "sk-..."
# Create collection
client.create_collection(
collection_name="knowledge_base",
vectors_config=VectorParams(size=1536, distance=Distance.COSINE),
)
# Embed and insert
text = "How to install Nginx on Ubuntu"
vector = openai.embeddings.create(
model="text-embedding-ada-002", input=text
).data[0].embedding
client.upsert(
collection_name="knowledge_base",
points=[PointStruct(id=1, vector=vector, payload={"text": text})]
)
# Semantic search
query_vec = openai.embeddings.create(
model="text-embedding-ada-002", input="configure web server"
).data[0].embedding
results = client.search(collection_name="knowledge_base", query_vector=query_vec, limit=5)
for r in results:
print(r.score, r.payload)
7 Nginx Reverse Proxy with TLS
server {
listen 443 ssl http2;
server_name qdrant.yourdomain.com;
ssl_certificate /etc/letsencrypt/live/qdrant.yourdomain.com/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/qdrant.yourdomain.com/privkey.pem;
location / {
proxy_pass http://127.0.0.1:6333;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
# Restrict to your application server IP
allow 203.0.113.10;
deny all;
}
}
8 Backup with Snapshots
# Create snapshot
curl -X POST http://localhost:6333/collections/my_docs/snapshots \
-H "api-key: YOUR_SECRET_API_KEY_HERE"
# List snapshots
curl http://localhost:6333/collections/my_docs/snapshots \
-H "api-key: YOUR_SECRET_API_KEY_HERE"
# Download snapshot
curl -o my_docs_backup.snapshot \
http://localhost:6333/collections/my_docs/snapshots/SNAPSHOT_NAME \
-H "api-key: YOUR_SECRET_API_KEY_HERE"
Nightly backup script via cron:
#!/bin/bash
DATE=$(date +%Y%m%d)
API_KEY="YOUR_SECRET_API_KEY_HERE"
BACKUP_DIR="/var/backups/qdrant"
mkdir -p "$BACKUP_DIR"
for col in $(curl -s http://localhost:6333/collections \
-H "api-key: $API_KEY" | jq -r '.result.collections[].name'); do
curl -s -X POST "http://localhost:6333/collections/$col/snapshots" \
-H "api-key: $API_KEY" > /dev/null
done
rsync -a ~/qdrant/snapshots/ "$BACKUP_DIR/$DATE/"
find "$BACKUP_DIR" -maxdepth 1 -type d -mtime +7 -exec rm -rf {} +
Verify Qdrant is Working Correctly
# Container status
docker compose ps
# Health endpoint
curl http://localhost:6333/healthz
# Prometheus metrics
curl http://localhost:6333/metrics | grep qdrant
# List all collections
curl http://localhost:6333/collections -H "api-key: YOUR_SECRET_API_KEY_HERE"
# Point count in a collection
curl http://localhost:6333/collections/my_docs \
-H "api-key: YOUR_SECRET_API_KEY_HERE" | jq '.result.points_count'
http://localhost:6333/dashboard. Use it to browse collections, run test searches, and inspect point payloads during development — no extra tool needed.Troubleshooting Common Issues
Error: "Wrong input: Not existing collection"
The collection has not been created yet, or the name is misspelled. List all collections to verify:
curl http://localhost:6333/collections -H "api-key: KEY"
# Create missing collection with PUT /collections/{name}
Error: "Unauthorized" or HTTP 403
The API key header is missing or incorrect. Check the environment variable inside the running container:
docker compose exec qdrant env | grep API_KEY
# Update docker-compose.yml, then restart
docker compose down && docker compose up -d
Container Crashes with Out-of-Memory
Qdrant keeps its index in RAM. Add swap space as a safety net:
docker stats qdrant --no-stream
sudo fallocate -l 4G /swapfile
sudo chmod 600 /swapfile
sudo mkswap /swapfile
sudo swapon /swapfile
echo '/swapfile none swap sw 0 0' | sudo tee -a /etc/fstab
Error: "Vector dimension mismatch"
The vector you are inserting does not match the size configured when the collection was created:
# Inspect collection config
curl http://localhost:6333/collections/my_docs \
-H "api-key: KEY" | jq '.result.config.params'
# The "size" value must equal len(your_vector)
Slow Startup Due to Large Index
Qdrant loads its HNSW index from disk into RAM at startup. For large collections this can take 30–60 seconds. Monitor progress:
docker compose logs -f qdrant | grep -i "loading\|loaded\|segment"
Frequently Asked Questions
How does Qdrant compare to Elasticsearch for vector search?
Elasticsearch added vector search support recently, but Qdrant was architected exclusively for vector workloads. In pure vector-search benchmarks Qdrant delivers higher QPS with lower RAM usage, and its payload filtering system is more expressive. Use Elasticsearch when you need combined full-text + vector search in a single cluster; otherwise Qdrant is the lighter, faster choice.
Does Qdrant require a GPU?
No. Qdrant itself runs entirely on CPU — it performs ANN search, not model inference. A GPU is only needed if you are generating embeddings locally (e.g., with a custom model via Ollama). When using OpenAI or another hosted embedding API, a CPU-only VPS is perfectly sufficient.
Can Qdrant handle multi-tenancy?
Yes. The common pattern is to store a tenant_id field in each point's payload and then apply a payload filter on every search query so that users only see their own data. For very large per-tenant datasets, dedicated collections per tenant offer stronger isolation and simpler index tuning.