> ## Documentation Index
> Fetch the complete documentation index at: https://docs.controlplane.com/llms.txt
> Use this file to discover all available pages before exploring further.

# Qdrant

> Deploy Qdrant on Control Plane using the Template Catalog. An open-source vector database for similarity search and RAG, with REST and gRPC APIs, persistent storage, optional API-key authentication, and scheduled volume snapshots. Covers the prerequisite key secret, access modes, client gotchas, and pairing with other AI templates.

## Overview

Qdrant is an open-source (Apache-2.0) vector database for similarity search and retrieval-augmented generation. This template deploys a single Qdrant 1.18 server with persistent storage, REST and gRPC APIs, optional API-key authentication, and scheduled platform volume snapshots. There is no feature gating — everything in the upstream open-source build is available.

### Architecture

* **Qdrant server** — A single-replica `stateful` workload (`{release}-qdrant`) serving the REST API and the built-in web dashboard on port `6333`, and the gRPC API on port `6334`.
* **Persistent data** — One volume set mounted at `/qdrant/data` holding collections, segments, HNSW indexes, the write-ahead log, **and** Qdrant's own logical snapshots. Both durable directories live on the same volume, so a snapshot you create through the API survives restarts and redeployments.
* **Authentication** *(optional)* — API keys come from a dictionary secret you create yourself and reference by name. A read-only key can be wired alongside the primary key.

### What Gets Created

* **Stateful Qdrant Workload** — A single replica serving REST on `6333` and gRPC on `6334`.
* **Volume Set** — Persistent storage at `/qdrant/data` with scheduled snapshots and a final snapshot on delete.
* **Identity** — An identity bound to the workload, used to read the API-key secret.
* **Policy** *(only when `auth.secretName` is set)* — `reveal` on exactly that one secret and nothing else.

<Note>
  This template does not create a GVC. You must deploy it into an existing GVC.
</Note>

## Prerequisites

**A default install has no prerequisites** — Qdrant needs no database, cache, or object store, and backups use platform volume snapshots rather than a cloud account or bucket.

**If you want authentication** (required before you can enable public access), create a **dictionary** secret first and reference it by name in `auth.secretName`:

```bash theme={null}
cpln secret create-dictionary --name my-qdrant-keys \
  --entry api-key=$(openssl rand -hex 32) \
  --entry read-only-api-key=$(openssl rand -hex 32)
```

* `api-key` — the primary key; full read and write access.
* `read-only-api-key` — optional, only needed when `auth.readOnlyKey: true`.

<Warning>
  Installing with `publicAccess.enabled: true` and an empty `auth.secretName` fails at render time. An internet-reachable vector database without a key would expose every collection to anyone who finds the endpoint, so the chart refuses to render that combination — create the secret first.
</Warning>

Install the template using your preferred method:

<CardGroup cols={2}>
  <Card title="UI" href="/template-catalog/install-manage/ui" icon="laptop">
    Browse, install, and manage templates visually
  </Card>

  <Card title="CLI" href="/template-catalog/install-manage/cli" icon="terminal">
    Manage templates from your terminal
  </Card>

  <Card title="Terraform" href="/template-catalog/install-manage/terraform" icon={<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 128 128"><g fill-rule="evenodd"><path d="M77.941 44.5v36.836L46.324 62.918V26.082zm0 0" fill="#5c4ee5"/><path d="M81.41 81.336l31.633-18.418V26.082L81.41 44.5zm0 0" fill="#4040b2"/><path d="M11.242 42.36L42.86 60.776V23.941L11.242 5.523zm0 0M77.941 85.375L46.324 66.957v36.82l31.617 18.418zm0 0" fill="#5c4ee5"/></g></svg>}>
    Declare templates in your Terraform configurations
  </Card>

  <Card
    title="Pulumi"
    href="/template-catalog/install-manage/pulumi"
    icon={<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" id="Pulumi-Icon--Streamline-Svg-Logos" height="24" width="24">
    <desc>
        Pulumi Icon Streamline Icon: https://streamlinehq.com
    </desc>
    <path fill="#f26e7e" d="M4.683025 13.3318c0.869125 -0.5018 0.870575 -2.1264 0.003225 -3.62865s-2.27504 -2.313275 -3.1441725 -1.811475C0.672945 8.3935 0.6715 10.0181 1.53885 11.52035c0.86735 1.502275 2.27505 2.313275 3.144175 1.81145Zm0.0052 3.2167c0.86735 1.502275 0.865925 3.126875 -0.003225 3.628675 -0.86915 0.5018 -2.2768275 -0.309225 -3.144175 -1.81145 -0.8673525 -1.50225 -0.8659075 -3.126875 0.003225 -3.628675 0.8691325 -0.5018 2.276825 0.309225 3.144175 1.81145Zm5.922875 3.4243c0.86735 1.50225 0.8659 3.126775 -0.003225 3.62875 -0.869125 0.501775 -2.27685 -0.309325 -3.1442 -1.81155 -0.867325 -1.50225 -0.865875 -3.12685 0.00325 -3.628675 0.869125 -0.5018 2.276825 0.309225 3.144175 1.811475Zm-0.001925 -6.845275c0.86735 1.50225 0.8659 3.12685 -0.003225 3.628675 -0.869125 0.5018 -2.276825 -0.309225 -3.144175 -1.811475 -0.86735 -1.50225 -0.8659 -3.12685 0.003225 -3.62865 0.869125 -0.501825 2.276825 0.3092 3.144175 1.81145Z" stroke-width="0.25"></path>
    <path fill="#8a3391" d="M22.45775 11.524125c0.86725 -1.502225 0.865925 -3.12685 -0.003225 -3.62865 -0.869125 -0.501825 -2.276825 0.3092 -3.144175 1.811475 -0.86735 1.50225 -0.8659 3.126825 0.003225 3.62865 0.869125 0.501825 2.276825 -0.3092 3.144175 -1.811475Zm0.000175 3.2151c0.869075 0.5018 0.870625 2.1264 0.003225 3.62865 -0.86735 1.50225 -2.27505 2.313275 -3.144175 1.81145 -0.869125 -0.5018 -0.870575 -2.126425 -0.003225 -3.62865 0.86735 -1.50225 2.27505 -2.313275 3.144175 -1.81145ZM16.536225 18.157875c0.86915 0.501825 0.8706 2.126425 0.00325 3.628675 -0.86735 1.502125 -2.275075 2.313225 -3.1442 1.81145 -0.869125 -0.50175 -0.870575 -2.126425 -0.003225 -3.62865 0.867375 -1.502275 2.27505 -2.3133 3.144175 -1.811475Zm-0.003325 -6.843775c0.869125 0.5018 0.870575 2.126425 0.003225 3.628675s-2.27505 2.313275 -3.1442 1.811475c-0.869125 -0.501825 -0.870575 -2.126425 -0.003225 -3.628675 0.86735 -1.502275 2.27505 -2.313275 3.1442 -1.811475Z" stroke-width="0.25"></path>
    <path fill="#f7bf2a" d="M15.138225 2.06721c0 1.003615 -1.40625 1.817215 -3.14095 1.817215 -1.7347 0 -3.14095 -0.8136 -3.14095 -1.817215C8.856325 1.06359 10.262575 0.25 11.997275 0.25c1.7347 0 3.14095 0.81359 3.14095 1.81721ZM9.2166 5.482375c0 1.003625 -1.40625 1.8172 -3.14095 1.8172 -1.7347 0 -3.14095 -0.813575 -3.14095 -1.8172s1.40625 -1.817225 3.14095 -1.817225c1.7347 0 3.14095 0.8136 3.14095 1.817225Zm8.71005 1.8172c1.7347 0 3.14095 -0.813575 3.14095 -1.8172s-1.40625 -1.817225 -3.14095 -1.817225c-1.7347 0 -3.14095 0.8136 -3.14095 1.817225s1.40625 1.8172 3.14095 1.8172Zm-2.788425 1.605625c0 1.003625 -1.40625 1.8172 -3.14095 1.8172 -1.7347 0 -3.14095 -0.813575 -3.14095 -1.8172 0 -1.0036 1.40625 -1.8172 3.14095 -1.8172 1.7347 0 3.14095 0.8136 3.14095 1.8172Z" stroke-width="0.25"></path>
    </svg>}
  >
    Declare templates in your Pulumi programs
  </Card>
</CardGroup>

## Configuration

The default `values.yaml` for this template:

```yaml theme={null}
# ─── Image ────────────────────────────────────────────────────────
image: qdrant/qdrant:v1.18.3

# ─── Resources ────────────────────────────────────────────────────
# HNSW vector indexes are RAM-resident by default. Rough sizing:
#   memory ≈ vectors × dimensions × 4 bytes × 1.5
#   (200k vectors × 1536 dims ≈ 1.8 GiB)
resources:
  minCpu: 250m
  maxCpu: 1000m
  minMemory: 1Gi
  maxMemory: 4Gi

# ─── Storage ──────────────────────────────────────────────────────
volumeset:
  capacity: 20 # GiB (platform minimum 10) — collections, segments, WAL, Qdrant snapshots

# ─── Backup (platform volume snapshots — no cloud account needed) ─
backup:
  enabled: true
  schedule: "0 3 * * *" # cron in UTC — daily 03:00 (hourly is the platform max)
  retention: 7d         # how long each snapshot is kept (e.g. 7d, 720h, 30d)

# ─── Authentication (optional, strongly recommended) ──────────────
# PREREQUISITE dictionary secret — create it BEFORE install:
#   cpln secret create-dictionary --name my-qdrant-keys \
#     --entry api-key=$(openssl rand -hex 32) \
#     --entry read-only-api-key=$(openssl rand -hex 32)
# Empty = no authentication; allowed ONLY while publicAccess is off.
auth:
  secretName: "" # e.g. my-qdrant-keys — REQUIRED when publicAccess.enabled is true
  readOnlyKey: false # true = also wire `read-only-api-key` from the same secret

# ─── Service ──────────────────────────────────────────────────────
service:
  dashboard: true # serve the built-in web UI at /dashboard (its static shell is unauthenticated)
  maxRequestSizeMb: 32 # max POST body in MB; an over-size upsert is rejected with HTTP 400 and writes nothing
  telemetryDisabled: true # true = send no anonymous usage reports upstream

# ─── Access ───────────────────────────────────────────────────────
publicAccess:
  enabled: false # true = REST API (+ dashboard) over HTTPS on the auto *.cpln.app endpoint; gRPC stays internal
internalAccess:
  type: same-gvc # none | same-gvc | same-org | workload-list
  workloads: [] # used only with workload-list
  # workloads:
  #   - //gvc/GVC_NAME/workload/WORKLOAD_NAME
```

### Image and resources

* `image` — The Qdrant server image. Pin a concrete tag.
* `resources.minCpu` / `resources.maxCpu` / `resources.minMemory` / `resources.maxMemory` — CPU reservation and limit, memory reservation and limit for the Qdrant container.

Memory is the sizing constraint. Vectors and their HNSW graphs are held in RAM unless a collection is explicitly created with `on_disk` vectors or index, so size roughly as `vectors × dimensions × 4 bytes × 1.5` — about 1.8 GiB for 200,000 vectors at 1536 dimensions. Raise `maxMemory` before loading a large collection.

### Storage

* `volumeset.capacity` — Initial volume size in GiB. The platform minimum is 10, and the chart rejects anything smaller at render time.

The single volume at `/qdrant/data` holds `storage/` (collections, segments, HNSW indexes, WAL) and `snapshots/` (Qdrant's own logical snapshots). Keeping both on the volume is deliberate: the upstream defaults place snapshots on the container's ephemeral layer, where they would be lost on every restart.

### Backup

Backups are **platform volume snapshots** of the data volume — no cloud account, bucket, or IAM policy is required.

* `backup.enabled` — When `true` (default), the volume set takes snapshots on `backup.schedule`.
* `backup.schedule` — Cron expression in UTC. Hourly is the platform maximum frequency.
* `backup.retention` — How long each snapshot is kept (for example `7d`, `720h`, `30d`).

The volume set is also configured to take a final snapshot when it is deleted, retained for `backup.retention`. That final snapshot is taken regardless of `backup.enabled` — setting it to `false` disables *scheduled* snapshots only.

These platform snapshots are independent of Qdrant's own collection snapshots, which you create through the API and which live on the same volume.

### Authentication

* `auth.secretName` — Name of your prerequisite dictionary secret. Empty (the default) leaves the API unauthenticated, which is permitted **only** while `publicAccess.enabled` is `false`.
* `auth.readOnlyKey` — When `true`, also wires the `read-only-api-key` entry from the same secret. Setting it without `auth.secretName` fails at render time.

Clients authenticate with an `api-key` header. The read-only key can search, scroll, and read collections, but write operations are rejected with `403 Forbidden: Global manage access is required`.

Qdrant's health paths (`/healthz`, `/livez`, `/readyz`) stay reachable without a key so the platform probes keep working with authentication on. `/metrics` is **not** exempt — it returns `401` without a key.

### Service

* `service.dashboard` — Serves Qdrant's built-in web UI at `/dashboard`. The static shell of that UI loads without a key even when authentication is on (its API calls do not), so set this to `false` on any publicly exposed instance.
* `service.maxRequestSizeMb` — Maximum POST body size in MB. An over-size upsert is rejected immediately with **HTTP 400** and the message `JSON payload (N bytes) is larger than allowed (limit: M bytes).` — nothing is partially written.
* `service.telemetryDisabled` — When `true` (default), Qdrant sends no anonymous usage reports upstream.

### Access

* `publicAccess.enabled` — When `true`, the REST API (and the dashboard, if enabled) is served over HTTPS on the automatically assigned `*.cpln.app` canonical endpoint. gRPC always stays internal. Requires `auth.secretName`. When `false` (default), external requests to the canonical hostname are refused with `403 RBAC: access denied`.
* `internalAccess.type` — Internal firewall scope of the workload:

| Type            | Description                                                            |
| --------------- | ---------------------------------------------------------------------- |
| `none`          | No internal access — in-GVC traffic is refused.                        |
| `same-gvc`      | Allow access from all workloads in the same GVC (default).             |
| `same-org`      | Allow access from all workloads in the same organization.              |
| `workload-list` | Allow access only from workloads listed in `internalAccess.workloads`. |

* `internalAccess.workloads` — Workload links (`//gvc/GVC_NAME/workload/WORKLOAD_NAME`), used only with `workload-list`.

## Connecting

| Target                                 | Address                                             | Credentials                                    |
| -------------------------------------- | --------------------------------------------------- | ---------------------------------------------- |
| Internal REST (same GVC)               | `http://{release}-qdrant.{gvc}.cpln.local:6333`     | `api-key` header when `auth.secretName` is set |
| Internal gRPC (same GVC)               | `{release}-qdrant.{gvc}.cpln.local:6334`            | Same API key                                   |
| Public REST + dashboard *(if enabled)* | `https://<canonical>.cpln.app` (UI at `/dashboard`) | `api-key` header                               |
| Health                                 | `GET /healthz`, `/livez`, `/readyz` on `6333`       | None — exempt from the API key                 |

The canonical hostname appears under `status.canonicalEndpoint` (`cpln workload get {release}-qdrant -o yaml`). Public traffic is HTTPS at the platform edge; same-GVC traffic is plain HTTP and gRPC carried over the mesh's own mTLS.

```bash theme={null}
curl -H "api-key: YOUR_API_KEY" \
  http://{release}-qdrant.{gvc}.cpln.local:6333/collections
```

### Python client — disable TLS for in-GVC calls

<Warning>
  `qdrant-client` silently switches to TLS as soon as an `api_key` is supplied. Against the internal endpoint, which speaks plain HTTP and gRPC, the call then hangs instead of failing cleanly — the symptom is a gRPC `DEADLINE_EXCEEDED`. Pass `https=False` (or use explicit `http://` URLs) for any client running inside the GVC.
</Warning>

```python theme={null}
from qdrant_client import QdrantClient

client = QdrantClient(
    host="{release}-qdrant.{gvc}.cpln.local",
    port=6333, grpc_port=6334, prefer_grpc=True,
    api_key="YOUR_API_KEY",
    https=False,   # REQUIRED: qdrant-client turns TLS on automatically when api_key is set
)
```

## Using Qdrant with Other Templates

Qdrant is the retrieval tier of a RAG stack, and every other component reaches it over internal GVC DNS with no public exposure. Deploy them into the same GVC and wire them by hostname:

| Template                                         | Internal address                                      | Role                                           |
| ------------------------------------------------ | ----------------------------------------------------- | ---------------------------------------------- |
| Qdrant                                           | `http://{release}-qdrant.{gvc}.cpln.local:6333`       | Vector store                                   |
| [Ollama](/template-catalog/templates/ollama)     | `http://{release}-ollama.{gvc}.cpln.local:11434`      | Local embedding and chat models                |
| [LiteLLM](/template-catalog/templates/litellm)   | `http://{release}-litellm.{gvc}.cpln.local:4000`      | OpenAI-compatible gateway to hosted models     |
| [Langfuse](/template-catalog/templates/langfuse) | `http://{release}-langfuse-web.{gvc}.cpln.local:3000` | Tracing for the retrieval and generation calls |

A retrieval service running in the GVC embeds with Ollama, stores in Qdrant, and generates through LiteLLM:

```python theme={null}
import httpx
from qdrant_client import QdrantClient
from qdrant_client.models import VectorParams, Distance, PointStruct

GVC = "my-gvc"
qdrant = QdrantClient(host=f"my-qdrant.{GVC}.cpln.local", port=6333, grpc_port=6334,
                      prefer_grpc=True, api_key=API_KEY, https=False)
qdrant.create_collection("docs", vectors_config=VectorParams(size=768, distance=Distance.COSINE))

# embed with the in-GVC ollama workload
vec = httpx.post(f"http://my-ollama.{GVC}.cpln.local:11434/api/embeddings",
                 json={"model": "nomic-embed-text", "prompt": text}).json()["embedding"]
qdrant.upsert("docs", points=[PointStruct(id=1, vector=vec, payload={"text": text})])

# retrieve, then generate through the in-GVC litellm gateway
hits = qdrant.query_points("docs", query=vec, limit=4).points
httpx.post(f"http://my-litellm.{GVC}.cpln.local:4000/v1/chat/completions", json={...})
```

[Open WebUI](/template-catalog/templates/open-webui) can also use Qdrant as its vector store instead of the bundled Chroma, via the environment variables `VECTOR_DB=qdrant`, `QDRANT_URI=http://{release}-qdrant.{gvc}.cpln.local:6333`, and `QDRANT_API_KEY`. The `open-webui` template does not expose these as values yet — set them on the deployed workload.

## Important Notes

* **Public access requires an API key.** Installing with `publicAccess.enabled: true` and an empty `auth.secretName` fails at render time. Create the dictionary secret first.
* **In-GVC clients must disable TLS.** `qdrant-client` turns TLS on automatically when an `api_key` is set and then hangs against the internal endpoint; the symptom is a gRPC `DEADLINE_EXCEEDED`. Pass `https=False` or use plain `http://` URLs.
* **The `/dashboard` shell loads without an API key** (its API calls do not). Set `service.dashboard: false` when Qdrant is publicly exposed.
* **Single replica by design.** Data survives restarts and upgrades on the volume set, but any upgrade or reschedule is a real outage of roughly 60–90 seconds — measured at 79 seconds with 313 consecutive failed requests — not a blip. Budget about 2.5 minutes for a configuration change to roll fully, and plan writes around it. Qdrant's distributed mode is supported upstream but requires stable per-peer addressing that this version does not implement; there is no `replicas` knob.
* **Over-size requests return HTTP 400, not 413.** A body larger than `service.maxRequestSizeMb` is rejected outright and no points are written — batch large upserts or raise the limit.
* **Uninstall deletes the volume set.** A final snapshot is retained for `backup.retention`, and your own API-key secret is left untouched.
* **Memory is the sizing constraint.** Vectors and HNSW graphs stay in RAM unless a collection is created with `on_disk` vectors or index. Raise `resources.maxMemory` before loading large collections.

## External References

<CardGroup cols={2}>
  <Card title="Qdrant Documentation" icon="book" href="https://qdrant.tech/documentation/">
    Official Qdrant documentation
  </Card>

  <Card title="Security and API Keys" icon="key" href="https://qdrant.tech/documentation/guides/security/">
    API-key authentication and read-only key behavior
  </Card>

  <Card title="Collections and Indexing" icon="database" href="https://qdrant.tech/documentation/concepts/collections/">
    Create collections, choose distance metrics, and tune HNSW
  </Card>

  <Card title="Snapshots" icon="camera" href="https://qdrant.tech/documentation/concepts/snapshots/">
    Qdrant's own collection snapshot and restore API
  </Card>

  <Card title="Memory Consumption" icon="memory" href="https://qdrant.tech/articles/memory-consumption/">
    Sizing guidance for vectors, indexes, and on-disk storage
  </Card>

  <Card title="Qdrant Template" icon="github" href="https://github.com/controlplane-com/templates/tree/main/qdrant">
    View the source files, default values, and chart definition
  </Card>
</CardGroup>
