How to Install Qdrant Vector Database on VPS for AI & RAG

Qdrant vector database on VPS for AI and RAG pipelines

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?

1 System Requirements

ResourceMinimumRecommended
RAM2 GB8 GB or more
CPU2 vCPU4+ vCPU
Disk20 GB SSD50+ GB NVMe
OSUbuntu 22.04+Ubuntu 24.04 / Debian 12
Note: Qdrant keeps its HNSW index in RAM for fast queries. A collection of 1 million 1536-dimension vectors (OpenAI ada-002 size) requires roughly 6–8 GB of RAM.

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"
    }
  }'
Tip: Choose the right distance metric — 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'
Tip: Qdrant ships a built-in Dashboard UI at 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.