Skip to main content
Qdrant Operator logo

Qdrant Operator

A Kubernetes operator for managing Qdrant vector database clusters

CI PyPI Python 3.12+ License: MIT Kubernetes

Documentation • Features • Installation • Usage • Configuration


Features

  • Cluster Management - Deploy and manage Qdrant clusters via Helm
  • Automated Backups - Per-node snapshots streamed to S3-compatible storage, with a manifest
  • Scheduled Backups - CronJob-style schedules with concurrency and retention policies
  • Disaster Recovery - Restore collections node-by-node from presigned URLs, with optional remapping
  • Declarative Collections - QdrantCollection keeps vectors, payload indexes and aliases in Git and re-creates what was deleted by hand
  • Least-Privilege Tokens - QdrantAccessKey signs Qdrant JWT tokens per collection and writes them into a Secret, renewing before expiry
  • Live Migrations - QdrantMigration copies collections between clusters (or from Qdrant Cloud) without an S3 round trip
  • Admission Validation - CEL rules on every CRD reject impossible specs before the operator sees them
  • Async-First - Built on kopf with fully async adapters for performance

Installation

Prerequisites

  • Kubernetes cluster (v1.26+)
  • Helm 3.x installed
  • kubectl configured with cluster access
# From the OCI registry (published on every release)
helm install qdrant-operator oci://ghcr.io/4thel00z/charts/qdrant-operator -n qdrant-system --create-namespace

# Or from the local chart
helm install qdrant-operator ./charts/qdrant-operator

# Or install in a specific namespace
helm install qdrant-operator ./charts/qdrant-operator -n qdrant-system --create-namespace

# Verify installation
kubectl get pods -l app.kubernetes.io/name=qdrant-operator

Upgrading

Helm installs the CRDs in crds/ once and never updates them. Before helm upgrade, apply the CRDs of the new version (they are attached to every GitHub release):

kubectl apply -f manifests/crds/
helm upgrade qdrant-operator oci://ghcr.io/4thel00z/charts/qdrant-operator -n qdrant-system

Configuration

Override default values:

helm install qdrant-operator ./charts/qdrant-operator \
  --set operator.logLevel=DEBUG \
  --set resources.limits.memory=512Mi

Or use a values file:

helm install qdrant-operator ./charts/qdrant-operator -f my-values.yaml

Install from PyPI

The operator is also a Python package; the qdrant-operator command runs it against your kubeconfig, forwarding any extra flags to kopf run (for example --namespace team-a).

uv tool install qdrant-operator   # or: pipx install qdrant-operator
qdrant-operator --help

Container images: ghcr.io/4thel00z/qdrant-operator:<version> (linux/amd64, linux/arm64, signed provenance).

Build Docker Image (Optional)

# Replace with your registry
docker build -t ghcr.io/YOUR_ORG/qdrant-operator:0.1.0 .
docker push ghcr.io/YOUR_ORG/qdrant-operator:0.1.0

Local Development

# Install dependencies
uv sync

# Apply CRDs to cluster
kubectl apply -f manifests/crds/

# Run operator locally against your kubeconfig
make dev

Usage

Create a Qdrant Cluster

apiVersion: qdrant.io/v1alpha1
kind: QdrantCluster
metadata:
  name: my-qdrant
  namespace: default
spec:
  replicas: 3
  version: v1.16.3
  resources:
    requests:
      cpu: "500m"
      memory: "1Gi"
    limits:
      cpu: "2"
      memory: "4Gi"
  persistence:
    size: 10Gi
    storageClassName: standard
  cluster:
    enabled: true

Create a Backup

apiVersion: qdrant.io/v1alpha1
kind: QdrantBackup
metadata:
  name: my-backup
  namespace: default
spec:
  clusterRef:
    name: my-qdrant
  storage:
    s3:
      bucket: my-backups
      prefix: qdrant
      region: us-east-1
      credentialsSecretRef:
        name: s3-credentials
        accessKeyIdKey: AWS_ACCESS_KEY_ID

Schedule Automated Backups

apiVersion: qdrant.io/v1alpha1
kind: QdrantBackupSchedule
metadata:
  name: daily-backup
  namespace: default
spec:
  schedule: "0 2 * * *"  # Daily at 2 AM
  clusterRef:
    name: my-qdrant
  storage:
    s3:
      bucket: my-backups
      prefix: scheduled
      region: us-east-1
      credentialsSecretRef:
        name: s3-credentials
        accessKeyIdKey: AWS_ACCESS_KEY_ID
  retentionPolicy:
    keepLast: 7
    keepDaily: 30
  concurrencyPolicy: Forbid

Restore from Backup

apiVersion: qdrant.io/v1alpha1
kind: QdrantRestore
metadata:
  name: my-restore
  namespace: default
spec:
  backupRef:
    name: my-backup
  targetClusterRef:
    name: my-qdrant-new
  collectionMapping:
    old_collection: new_collection

Declare a Collection

apiVersion: qdrant.io/v1alpha1
kind: QdrantCollection
metadata:
  name: articles
  namespace: default
spec:
  clusterRef:
    name: my-qdrant
  collectionName: Articles        # optional; Qdrant names may use characters Kubernetes names cannot
  vectors:
    - name: text
      size: 768
      distance: Cosine
      memory: cold                # Qdrant 1.19+; use onDisk: true on older releases
    - name: image
      size: 512
      distance: Dot
  sparseVectors:
    - name: bm25
      modifier: idf
  replicationFactor: 2
  optimizers:                     # Qdrant's own field names, passed through
    indexing_threshold: 10000
  payloadIndexes:
    - field: tenant_id
      type: keyword
      params: {is_tenant: true}
    - field: body
      type: text
      params: {tokenizer: word, lowercase: true}
  aliases: [articles-live]
  metadata: {owner: search-team}  # merged into the collection's metadata
  deletionPolicy: Retain          # Delete drops the collection with the resource

Custom sharding declares its keys in the same resource; keys are created when missing and never dropped, because dropping one deletes its points:

spec:
  shardingMethod: custom
  shardNumber: 1                  # shards per key
  shardKeys:
    - key: eu
    - key: us
      replicationFactor: 2

A single entry without name is Qdrant's unnamed vector. Vector size and distance, shardNumber and shardingMethod cannot change on a live collection; a mismatch puts the resource into phase: Degraded with a Ready=False condition instead of silently ignoring the spec. Everything else is patched in place, and only fields you declared are compared. Indexes and aliases the operator did not create are left alone.

Issue a Scoped Token

Requires spec.apiKey.jwtRbac: true on the cluster (tokens are signed with the cluster's API key).

apiVersion: qdrant.io/v1alpha1
kind: QdrantAccessKey
metadata:
  name: billing-reader
  namespace: default
spec:
  clusterRef:
    name: my-qdrant
  collections:                    # or: access: r | m  for cluster-wide tokens
    - name: invoices
      access: r                   # r, rw, or prw (points only, no snapshots or indexes)
  subject: billing-service
  ttl: 720h
  renewBefore: 24h                # default: a third of ttl
  secretName: billing-qdrant      # default: metadata.name

The Secret gets token and url keys and is garbage-collected with the resource. The token is re-issued when the spec changes, the cluster's API key rotates, the Secret disappears, or renewBefore is reached.

Migrate Between Clusters

apiVersion: qdrant.io/v1alpha1
kind: QdrantMigration
metadata:
  name: cloud-to-k8s
  namespace: default
spec:
  source:
    endpoint:                     # or: clusterRef: {name: old-qdrant}
      url: https://xyz.eu-central.aws.cloud.qdrant.io:6333
      apiKeySecretRef: {name: qdrant-cloud, key: api-key}
  targetClusterRef:
    name: my-qdrant
  collections: [articles]         # empty = all
  collectionMapping:
    articles: articles_v2
  batchSize: 500
  batchDelayMs: 0                 # throttle
  ensurePayloadIndexes: true      # also on targets that already exist
  target:                         # for collections the migration creates
    replicationFactor: 2
    # shardKey: tier-a            # route every point to one shard key
    # shardKeyField: tenant       # or route by a payload field

Points are scrolled from the source and upserted on the target, so the run is idempotent and a one-node source can become a three-node target (shard counts follow the target unless set under target). Missing target collections are created from the source configuration, including payload indexes; existing targets must have the same vector sizes and distances. On a custom-sharded target, each point keeps its source shard key (keys are created as they are met) unless routed with target.shardKey / target.shardKeyField; an auto-sharded target receives the points without shard keys. Progress and the scroll offset live in status, so a run interrupted by an operator restart resumes where it stopped.

Configuration

Environment Variables

Variable Description Default
KUBECONFIG Path to kubeconfig file In-cluster config
LOG_LEVEL Logging level INFO
LOG_FORMAT json or text json

S3 Credentials Secret

apiVersion: v1
kind: Secret
metadata:
  name: s3-credentials
type: Opaque
stringData:
  AWS_ACCESS_KEY_ID: "your-access-key"
  AWS_SECRET_ACCESS_KEY: "your-secret-key"

Development

# Install dependencies
uv sync

# Run tests
uv run pytest

# Run integration tests (requires k8s cluster + helm)
make test-integration

# Lint and format
uv run ruff check src tests
uv run ruff format src tests

# Type checking
uv run pyright src tests

Releasing

CI (.github/workflows/ci.yml) runs lint, pyright, unit tests, chart lint, a wheel/image build and the kind-based end-to-end suite on every push and pull request.

Releases are driven by release-please from the conventional commit history, all inside .github/workflows/release.yml on pushes to main:

  1. A release PR is kept up to date that bumps pyproject.toml and Chart.yaml, refreshes uv.lock and updates CHANGELOG.md.
  2. Merging that PR creates the vX.Y.Z tag and the GitHub release, and the same run publishes the wheel and sdist to PyPI (trusted publishing, no token), pushes the multi-arch image and the Helm chart to ghcr.io, and attaches the artifacts and CRDs to the release.

No secrets are involved beyond the default GITHUB_TOKEN; the repository setting Allow GitHub Actions to create and approve pull requests must be on for the release PR to appear.

License

MIT License - see LICENSE for details.

Metadata

Release files for qdrant-operator 0.3.1

For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.

Source distribution (sdist)

Source distribution for qdrant-operator 0.3.1
File Size Uploaded
qdrant_operator-0.3.1.tar.gz 40.3 kB Details

Built distribution (wheel)

Table of built distributions (wheels) for qdrant-operator 0.3.1
File Interpreter ABI Platform
qdrant_operator-0.3.1-py3-none-any.whl Python 3 none any Details

Total release size: 85.9 kB

Release files / qdrant_operator-0.3.1.tar.gz

Download URL qdrant_operator-0.3.1.tar.gz
Size 40.3 kB
Tags Source
SHA-256 checksum
How to use checksums
e9d59e61efae1806ecd1506b8f4355eeeb1d7a7d0120fe1a2ae674cd4cb52fc3
BLAKE2b-256 checksum
How to use checksums
9f01f563fab0f273a36e8a97778fb9000f25ca066701c9cb1c72b575162a99e2
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.14

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Sep 24, 2026.

Transparency log

Release files / qdrant_operator-0.3.1-py3-none-any.whl

Download URL qdrant_operator-0.3.1-py3-none-any.whl
Size 45.6 kB
Tags Python 3
SHA-256 checksum
How to use checksums
c354a45c5be472dcc30aed0f0c94f15bd10f58b79a80bf1ce45ab7eb864f2615
BLAKE2b-256 checksum
How to use checksums
34e7506cac361446180007430c1f28bb81cacfc1350e9033aeef3995285ea39d
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.14

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Sep 24, 2026.

Transparency log

Release history Release notifications | RSS feed

0.3.2

2 release files

This release

0.3.1 This release

2 release files

0.3.0

2 release files

0.2.1

2 release files

0.2.0

2 release files

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page