> ## 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

> Qdrant open-source vector database for similarity search and RAG, deployed as a single-replica server with REST and gRPC APIs, persistent storage, scheduled volume snapshots, and optional API-key authentication. Deploys into an existing GVC; a default install needs nothing created first.

## 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.

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

### What Gets Created

| Resource | Name | Purpose | Optional |
| - | - | - | - |
| Workload | `RELEASE_NAME-qdrant` | Qdrant server (stateful, 1 replica): REST API and web dashboard on `6333`, gRPC API on `6334` | — |
| Volume set | `RELEASE_NAME-qdrant-data` | Data volume mounted at `/qdrant/data` holding collections, segments, HNSW indexes, the write-ahead log, and Qdrant's own collection snapshots; takes scheduled platform snapshots and a final snapshot on delete | — |
| Identity | `RELEASE_NAME-qdrant-identity` | Workload identity used to read the API-key secret | — |
| Policy | `RELEASE_NAME-qdrant-policy` | Grants the identity `reveal` on exactly the one secret named in `auth.secretName` | `auth.secretName` set |

## Prerequisites

None for a default install. Qdrant needs no database, cache, or object store, and backups use platform volume snapshots rather than a cloud account or bucket.

Authentication is optional but strongly recommended, and it is **required** before you can enable public access. It is driven by a dictionary secret you create yourself and reference by name:

<Steps>
  <Step title="Create the API-key secret">
    Create a **dictionary** secret with an `api-key` entry. The `read-only-api-key` entry is only needed when you set `auth.readOnlyKey: true`.

    ```bash theme={null}
    cpln secret create-dictionary --name SECRET_NAME \
      --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` — can search, scroll, and read collections; writes are rejected.

    Set `auth.secretName` to this name.
  </Step>
</Steps>

<Warning>
  The secret must exist **before** you install with `auth.secretName` set. If it does not, the deployment wedges silently: `cpln logs` returns nothing, and the only place the missing secret is named is `status.versions[].message` in `cpln workload get-deployments RELEASE_NAME-qdrant --gvc GVC_NAME -o yaml`. Create the secret and the deployment recovers on its own after a few minutes, or run `cpln workload force-redeployment RELEASE_NAME-qdrant --gvc GVC_NAME` to skip the wait.
</Warning>

## Installation

A default install needs no `--set` values. To enable authentication, add `--set auth.secretName=SECRET_NAME` (the secret must already exist).

```bash theme={null}
cpln helm install RELEASE_NAME oci://ghcr.io/controlplane-com/templates/qdrant \
  --version 1.0.0 --gvc GVC_NAME
```

To install, follow the instructions for 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

### Image

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

### Resources

```yaml theme={null}
# 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
```

Memory is the sizing constraint. Vectors and their HNSW graphs stay in RAM unless a collection is created with `on_disk` vectors or index, so raise `maxMemory` before loading a large collection.

### Storage

```yaml theme={null}
volumeset:
  capacity: 20 # GiB (platform minimum 10) — collections, segments, WAL, Qdrant snapshots
```

The chart rejects a capacity below 10 at render time. The single volume at `/qdrant/data` holds both `storage/` (collections, segments, HNSW indexes, WAL) and `snapshots/` (Qdrant's own collection snapshots), so snapshots you create through the API survive restarts and redeployments.

### Backup

```yaml theme={null}
# ─── 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)
```

These are platform volume snapshots of the whole data volume. The volume set also takes a final snapshot when it is deleted, retained for `backup.retention`, regardless of `backup.enabled` — setting it to `false` disables scheduled snapshots only. They are independent of Qdrant's own collection snapshots, which you create through the API (see [Backing Up](#backing-up)).

### Authentication

```yaml theme={null}
# 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
```

Clients authenticate with an `api-key` header. The read-only key can search, scroll, and read collections; write operations are rejected with `403 Forbidden: Global manage access is required`. Qdrant's health paths (`/healthz`, `/livez`, `/readyz`) stay reachable without a key; `/metrics` is not exempt and returns `401` without one.

### Service

```yaml theme={null}
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
```

The static shell of the dashboard loads without a key even when authentication is on (its API calls do not), so set `service.dashboard: false` on any publicly exposed instance.

### Access

```yaml theme={null}
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
```

`publicAccess.enabled: true` requires `auth.secretName`; the chart refuses to render without it. gRPC always stays internal.

| `internalAccess.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 org. |
| `workload-list` | Allow access only from the workload links in `internalAccess.workloads`. |

## Connecting

| What | Address | Notes |
| - | - | - |
| Internal REST API (same GVC) | `http://RELEASE_NAME-qdrant.GVC_NAME.cpln.local:6333` | Plain HTTP; `api-key` header when `auth.secretName` is set |
| Internal gRPC API (same GVC) | `RELEASE_NAME-qdrant.GVC_NAME.cpln.local:6334` | Plain gRPC (no TLS); same API key |
| Public REST API and dashboard | `status.canonicalEndpoint` from `cpln workload get RELEASE_NAME-qdrant --gvc GVC_NAME -o yaml`; UI at `/dashboard` | Only with `publicAccess.enabled: true`; HTTPS; `api-key` header required |
| Health | `/healthz`, `/livez`, `/readyz` on `6333` | No API key needed |
| Credentials | `cpln secret reveal SECRET_NAME -o yaml` | Your prerequisite secret's `api-key` and `read-only-api-key` entries |

Verify from your machine with a port-forward (works with public access off):

```bash theme={null}
cpln port-forward RELEASE_NAME-qdrant 6333:6333 --gvc GVC_NAME
```

Then, in a second terminal:

```bash theme={null}
curl -H "api-key: YOUR_API_KEY" http://localhost:6333/collections
```

### Python Client

<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_NAME-qdrant.GVC_NAME.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. Each address below uses that template's own release name:

| Template | Internal address | Role |
| - | - | - |
| Qdrant | `http://RELEASE_NAME-qdrant.GVC_NAME.cpln.local:6333` | Vector store |
| [Ollama](/template-catalog/templates/ollama) | `http://RELEASE_NAME-ollama.GVC_NAME.cpln.local:11434` | Local embedding and chat models |
| [LiteLLM](/template-catalog/templates/litellm) | `http://RELEASE_NAME-litellm.GVC_NAME.cpln.local:4000` | OpenAI-compatible gateway to hosted models |
| [Langfuse](/template-catalog/templates/langfuse) | `http://RELEASE_NAME-langfuse-web.GVC_NAME.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 (replace the hostnames with your own release names):

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

qdrant = QdrantClient(host="RELEASE_NAME-qdrant.GVC_NAME.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("http://RELEASE_NAME-ollama.GVC_NAME.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("http://RELEASE_NAME-litellm.GVC_NAME.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_NAME-qdrant.GVC_NAME.cpln.local:6333`, and `QDRANT_API_KEY`. The `open-webui` template does not expose these as values yet — set them on the deployed workload.

## Operations

### Backing Up

Two different things are called "snapshots", and they complement each other:

**Platform volume snapshots** (`backup.*`) are block-level snapshots of the whole data volume, taken on `backup.schedule` and on delete. List them, or take one on demand:

```bash theme={null}
cpln volumeset snapshot get RELEASE_NAME-qdrant-data --gvc GVC_NAME
cpln volumeset snapshot create RELEASE_NAME-qdrant-data --gvc GVC_NAME --snapshot-name pre-upgrade
```

**Qdrant collection snapshots** are logical, per-collection archives you create through the REST API. With a port-forward open (see [Connecting](#connecting)):

```bash theme={null}
# create a snapshot of one collection; the response carries its name
curl -X POST -H "api-key: YOUR_API_KEY" \
  http://localhost:6333/collections/COLLECTION_NAME/snapshots

# list the snapshots of that collection
curl -H "api-key: YOUR_API_KEY" \
  http://localhost:6333/collections/COLLECTION_NAME/snapshots

# download one to your machine
curl -H "api-key: YOUR_API_KEY" -o COLLECTION_NAME.snapshot \
  http://localhost:6333/collections/COLLECTION_NAME/snapshots/SNAPSHOT_NAME
```

Collection snapshot files are written to `/qdrant/data/snapshots/COLLECTION_NAME/` on the data volume, so they survive restarts and are included in platform volume snapshots. Keep a downloaded copy of anything you cannot afford to lose — an uninstall deletes the volume.

Qdrant can also take a full-storage snapshot (`POST /snapshots`), but upstream restores it only through a startup flag that this template does not expose. Use collection snapshots instead.

### Restoring a Backup

<Note>
  The procedures below follow the upstream snapshot documentation and the chart's configured snapshot path. They have not been exercised against a live install of this template.
</Note>

**From a downloaded collection snapshot.** Upload the `.snapshot` file to the server (a fresh install works, as long as it runs the same or the next minor version of Qdrant). `priority=snapshot` makes the snapshot data win over whatever the target collection currently holds; without it, recovering into an empty collection leaves it empty. With a port-forward open:

```bash theme={null}
curl -X POST -H "api-key: YOUR_API_KEY" \
  -F "snapshot=@COLLECTION_NAME.snapshot" \
  "http://localhost:6333/collections/COLLECTION_NAME/snapshots/upload?priority=snapshot"
```

**From a collection snapshot still on the data volume.** Point the recover endpoint at the file's location under the chart's snapshot path:

```bash theme={null}
curl -X PUT -H "api-key: YOUR_API_KEY" -H "Content-Type: application/json" \
  -d '{"location": "file:///qdrant/data/snapshots/COLLECTION_NAME/SNAPSHOT_NAME", "priority": "snapshot"}' \
  http://localhost:6333/collections/COLLECTION_NAME/snapshots/recover
```

**From a platform volume snapshot.** This replaces the entire data volume with the snapshot's contents and restarts the Qdrant replica; anything written after the snapshot is lost. It is issued as a `restoreVolume` command on the volume set — see the [volume set reference](/reference/volumeset#restorevolume). The equivalent CLI form, which has not been exercised on this template, is:

```bash theme={null}
cpln volumeset snapshot restore RELEASE_NAME-qdrant-data --gvc GVC_NAME \
  --snapshot-name SNAPSHOT_NAME --location LOCATION --volume-index 0
```

### Rotating the API Key

The running replica reads `cpln://secret/...` references once, at start. After you change the `api-key` (or `read-only-api-key`) entry in your dictionary secret, the old key keeps working until the workload restarts — force a redeployment to apply the new one:

```bash theme={null}
cpln workload force-redeployment RELEASE_NAME-qdrant --gvc GVC_NAME
```

Update your clients with the new key at the same time; the restart is a brief outage like any other.

### Scaling and Availability

* **Single replica by design.** There is no `replicas` knob: the volume set is per-replica, so a second replica would serve a separate, empty store, and Qdrant's distributed mode is not part of this version.
* **Upgrades and reschedules are a real outage.** Any `helm upgrade`, configuration change, or reschedule takes the API fully offline until the replacement replica is ready. Plan writes around it. Data, collections, and Qdrant snapshots on the volume survive.
* **Scale vertically.** Raise `resources.maxMemory` before loading a large collection, or have the client create collections with `on_disk` vectors and index. Changing resources is a `helm upgrade`, with the outage above.

### Troubleshooting

<AccordionGroup>
  <Accordion title="Install fails: publicAccess.enabled requires auth.secretName">
    **Symptom:** `cpln helm install` fails at render time with `qdrant: publicAccess.enabled requires auth.secretName — never expose Qdrant publicly without an API key.`

    **Cause:** The chart refuses to expose an unauthenticated vector database to the internet.

    **Fix:** Create the dictionary secret described in [Prerequisites](#prerequisites) and set `auth.secretName` to its name, or leave `publicAccess.enabled: false`.
  </Accordion>

  <Accordion title="Install fails: auth.readOnlyKey requires auth.secretName">
    **Symptom:** Render fails with `qdrant: auth.readOnlyKey requires auth.secretName.`

    **Cause:** The read-only key is read from the same secret as the primary key, so it cannot be enabled on its own.

    **Fix:** Set `auth.secretName`, and make sure the secret has a `read-only-api-key` entry.
  </Accordion>

  <Accordion title="Install fails: volumeset.capacity must be at least 10">
    **Symptom:** Render fails with `qdrant: volumeset.capacity must be at least 10 (GiB, platform minimum)`.

    **Fix:** Set `volumeset.capacity` to `10` or more.
  </Accordion>

  <Accordion title="Deployment never becomes ready after setting auth.secretName">
    **Symptom:** The workload stays not ready and `cpln logs` returns no lines at all.

    **Cause:** The secret named in `auth.secretName` does not exist, or its `api-key` (or `read-only-api-key`) entry is missing. Check `status.versions[].message` in `cpln workload get-deployments RELEASE_NAME-qdrant --gvc GVC_NAME -o yaml` — it names the missing secret.

    **Fix:** Create the secret with the exact entry names, then wait a few minutes or run `cpln workload force-redeployment RELEASE_NAME-qdrant --gvc GVC_NAME`.
  </Accordion>

  <Accordion title="Python client hangs with gRPC DEADLINE_EXCEEDED">
    **Symptom:** A `qdrant-client` call from inside the GVC never returns, then fails with `DEADLINE_EXCEEDED`.

    **Cause:** The client enabled TLS automatically because `api_key` was set, but the internal endpoint speaks plain HTTP and gRPC.

    **Fix:** Pass `https=False` to `QdrantClient`, or use explicit `http://` URLs.
  </Accordion>

  <Accordion title="403 Forbidden: Global manage access is required">
    **Symptom:** Reads work but an upsert, collection create, or delete returns `403 Forbidden: Global manage access is required`.

    **Cause:** The request used the `read-only-api-key`.

    **Fix:** Use the primary `api-key` for write operations.
  </Accordion>

  <Accordion title="HTTP 400: JSON payload is larger than allowed">
    **Symptom:** A large upsert is rejected with `JSON payload (N bytes) is larger than allowed (limit: M bytes).` and no points are written.

    **Cause:** The request body exceeded `service.maxRequestSizeMb` (32 MB by default).

    **Fix:** Send smaller batches, or raise `service.maxRequestSizeMb` and run `helm upgrade`.
  </Accordion>

  <Accordion title="401 from /metrics with authentication enabled">
    **Symptom:** `/healthz`, `/livez`, and `/readyz` answer without a key, but `/metrics` returns `401`.

    **Cause:** Only the health paths are exempt from the API key.

    **Fix:** Send the `api-key` header when scraping `/metrics`.
  </Accordion>

  <Accordion title="403 RBAC: access denied from the canonical endpoint">
    **Symptom:** Requests to the public `*.cpln.app` hostname are refused with `RBAC: access denied`.

    **Cause:** `publicAccess.enabled` is `false`, or it was just switched on and the change has not finished propagating.

    **Fix:** Set `publicAccess.enabled: true` together with `auth.secretName`, run `helm upgrade`, and allow a few minutes before retrying.
  </Accordion>

  <Accordion title="Container restarts while loading a large collection">
    **Symptom:** Uploads stall and `cpln workload get-deployments RELEASE_NAME-qdrant --gvc GVC_NAME -o yaml` shows restarts with reason `OOMKilled`; nothing is logged by Qdrant itself.

    **Cause:** The HNSW index outgrew `resources.maxMemory`.

    **Fix:** Raise `resources.maxMemory`, or create the collection with `vectors.on_disk: true` and `hnsw_config.on_disk: true` — a per-collection client choice, not a template knob.
  </Accordion>
</AccordionGroup>

## 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; 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.** Every upgrade or reschedule takes the API fully offline until the new replica is ready; plan writes around it. There is no `replicas` knob.
* **Rotating the API key needs a redeployment.** The new value is not picked up until you run `cpln workload force-redeployment RELEASE_NAME-qdrant --gvc GVC_NAME`.
* **Over-size requests return HTTP 400, not 413,** and write nothing. Batch large upserts or raise `service.maxRequestSizeMb`.
* **Two kinds of snapshots.** `backup.*` controls platform volume snapshots; Qdrant's collection snapshots are created through the API and live on the same volume.
* **Uninstall deletes the volume set.** A final snapshot is retained for `backup.retention`, a reinstall starts empty, 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/security/">
    API-key authentication and read-only key behavior
  </Card>

  <Card title="Snapshots" icon="camera" href="https://qdrant.tech/documentation/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>
