Qdrant on Kubernetes without Helm, with Kapitan
Qdrant 1.20.0, a vector database with a REST and a gRPC API, as one node with its data on a claim and an API key required on every request, rendered by the kadet-hub kubernetes generator 1.14.0 from Qdrant’s own Helm chart.
- Latest version:
1.0.0, published - Source chart:
https://qdrant.github.io/qdrant-helm/qdrant:1.20.0@sha256:dbd9a79e4d00e9d4fc117aa0a568b53ccf07f13dd8d26ac858b76fa14eb36bac - Proof level:
deploys, compared with the chart by the kadet-hub/recipes CI renderspassed in the release run of with check 0.18.0, kind v0.33.0- Output capabilities: none
- Inventory backends:
omegaconf - Generators: kubernetes 1.14.0
sha256:787129e1eaed6b9bc868fe4c4aec58d40de6a89fc3cc08424a4cb5822b855080 - Image security: grade D 200 findings, one per advisory and package, in the 1 image of version 1.0.0: CRITICAL 0, HIGH 56, MEDIUM 78, LOW 66, UNKNOWN 0. Scanned 2026-11-07 11:47 UTC. Full report
The grade rates the upstream images the recipe pins, not the recipe:docker.io/qdrant/qdrant.
What it deploys
recipes.qdrant(init.yml)recipes.qdrant.qdrant(qdrant.yml):qdrant
Required parameters
applications.qdrant.api_key is the API key that Qdrant requires on every
request, in the header api-key or as Authorization: Bearer <key>. Set it
as a ref with the backend of your choice. Without it the compile stops with
Interpolation key 'applications.qdrant.api_key' not found. The recipe
puts it into the Secret qdrant-apikey, key api-key, from which clients
in the namespace can read it.
Include
A target includes the recipe and its options as classes and can override anything the recipe sets:
classes:
- recipes.qdrant
parameters:
namespace: qdrant
kapitan:
vars:
target: qdrant
applications:
qdrant:
api_key: <ref>
Fetching it
The quickstart goes through these steps on a kind cluster with the uptime-kuma recipe, from installing Kapitan to the running app.
A separate fetch inventory pins the recipe by version and digest. This is fetch/targets/recipes.yml for version 1.0.0:
parameters:
kapitan:
dependencies:
- type: oci
source: ghcr.io/kadet-hub/recipes/qdrant:1.0.0@sha256:48d3dadaf619d3bf4d640abbfe48a4f46fb1c0ba988b6436531583a8feb6794b
output_path: inventory/classes/recipes/qdrant
The first command fetches the recipe into inventory/classes/recipes/qdrant, the second the generators and compiles:
kapitan compile --inventory-path fetch --fetch --output-path .
kapitan compile --fetch
The first command exits with status 1 because the fetch target compiles nothing. Kapitan's --fetch checks neither the digest nor the attestation. This verifies the attestation of the recipe:
gh attestation verify \
oci://ghcr.io/kadet-hub/recipes/qdrant@sha256:48d3dadaf619d3bf4d640abbfe48a4f46fb1c0ba988b6436531583a8feb6794b \
--owner kadet-hub \
--cert-identity https://github.com/kadet-hub/registry/.github/workflows/release.yml@refs/heads/main
The attested index must also list each digest under its recipe with the pinned version, and not as yanked. Verifying by hand shows how to read it.
Settings
| Chart value | Inventory |
|---|---|
image.tag |
components.qdrant.image, tag and digest |
apiKey |
applications.qdrant.api_key |
env |
components.qdrant.env.<variable>, such as QDRANT__TELEMETRY_DISABLED: "true" |
persistence.size |
components.qdrant.volume_claims.qdrant-storage.spec.resources.requests.storage |
persistence.storageClassName |
components.qdrant.volume_claims.qdrant-storage.spec.storageClassName |
resources |
components.qdrant.resources |
config |
the key production.yaml of generators.kubernetes.configs.qdrant.data |
service.type |
components.qdrant.additional_services.qdrant.type |
podAnnotations |
components.qdrant.pod_annotations |
nodeSelector, tolerations, affinity |
components.qdrant.node_selector, components.qdrant.tolerations, components.qdrant.affinity |
Qdrant reads QDRANT__<SECTION>__<KEY> variables over its configuration
file, so env is the shorter way to change one setting. With the chart’s
defaults Qdrant sends anonymous usage statistics to Qdrant, which
QDRANT__TELEMETRY_DISABLED: "true" turns off.
The recipe runs one node. replicaCount is not a setting, because the
generator cannot render the chart’s podManagementPolicy: Parallel, and
with the API default OrderedReady the chart warns that the nodes of a
cluster wait for each other forever.
The pod annotation checksum/config is the chart’s hash for the values in
tests/values.yaml. It does not change with the ConfigMap qdrant, so after
changing it restart the pod with kubectl rollout restart statefulset/qdrant.
A changed API key needs the same restart.
The script initialize.sh in the ConfigMap qdrant is the chart’s,
rendered for one node without snapshot restoration. It is Apache-2.0, and
its license is in qdrant.yml.LICENSE.
Any list set in the target is appended to the recipe’s list. To replace
one, set the key to ~ in a class included after the recipe and the new
list in the target.
Left out
The chart’s test runs only inside helm test. tests/smoke checks more on
the installed recipe. It is not rendered: ConfigMap/qdrant-test-db-interaction,
Pod/qdrant-test-db-interaction.
Proof
check qdrant --level deploys with tests/values.yaml and
tests/target.yml, on kind for each Kubernetes minor CI tests.
tests/values.yaml switches to the unprivileged image, adds what Pod
Security restricted asks for beyond the chart’s defaults, sets the API
key from the recipe’s Secret, and sets podManagementPolicy: OrderedReady
and the label name on the claim template, which the generator renders.
The objects differ in five ways, all of classes check accepts. The
StatefulSet and both Services select on name, the chart on app and
app.kubernetes.io/*, and both select the same pods. The Services target
the container ports by name, the chart by number. The container ports are
sorted by name. The pod and the ServiceAccount set
automountServiceAccountToken: true, which the chart leaves unset. The
Secret qdrant-apikey exists only in the recipe, and the chart’s pod reads
it. The chart binds no role.
tests/smoke requires that the namespace would pass Pod Security
restricted, and the image tag’s version from / through the Service. It
gets 401 without the API key and with a wrong one. With the key of the
Secret it creates a collection, writes three points with vectors and
payload, and gets the nearest point with its payload from a query. It
deletes the pod qdrant-0, waits for its successor on the same claim, and
finds the three points and the same nearest point again.
Versions
1.0.0, :sha256:48d3dadaf619d3bf4d640abbfe48a4f46fb1c0ba988b6436531583a8feb6794b
The recipe's files are in kadet-hub/recipes. All recipes