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

# What you provide

> The substrate Customer-Hosted Mixpeek expects from your Kubernetes cluster

You grant Mixpeek a namespace. Mixpeek deploys the whole stack into it: the API
and services, MVS, Redis, and a `RayService` running our own Ray, image and CUDA
userspace. Batch runs as ephemeral `RayJob`s in the same namespace.

This page is the list of what has to be true of your cluster first. The
[qualification suite](/docs/customer-hosted/qualification) checks every item on it and
gives you the report before anything is installed.

## Cluster

* **KubeRay already installed**, either cluster-wide or watching the namespace you
  grant. Mixpeek does not install it and does not need to own it. This is the one
  cluster-scoped prerequisite, and
  [Security and RBAC](/docs/customer-hosted/security-rbac) covers why and for how long.
* **KubeRay 1.1 or newer.** Hard requirement. Below it `submissionMode: HTTPMode`
  does not exist and batch cannot be submitted at all.
* **Kubernetes 1.33 or newer is optional.** From 1.33 Ray's autoscaler can use
  in-place vertical scaling (`pods/resize`), which the kit's autoscaler Role
  grants. On older clusters that rule is inert and Ray replaces pods to resize
  them.
* One namespace, and the two Roles from
  [Security and RBAC](/docs/customer-hosted/security-rbac).
* A `ResourceQuota` you are comfortable with. One ships with the kit. Change the
  numbers; Mixpeek reads it and sizes batches to fit.

## Compute

Node capacity the namespace can schedule onto, with the taints and labels we
should target. Tell us the labels and we render them into the deployment rather
than assuming yours look like ours.

GPU nodes at a known NVIDIA driver baseline for inference and extraction. The
suite reports GPU nodes it can see and whether an untolerating pod can actually
reach them, which is a different question from whether the nodes exist.

### The floor, before a single document

The install brings up thirteen Ray Serve applications and holds them warm. Their
replica minimums multiply out to **10.1 CPU sitting idle**, and **31.9 CPU** if
every one of them scales to its ceiling under load. That is the engine alone. It
does not count the API, Celery, Redis, or the MongoDB you bring.

None of the thirteen requests a GPU. The extractors that use one take it through
the batch path, not through a warm Serve replica, so a cluster with no GPU nodes
brings the whole plane up healthy and then fails at extraction time rather than
at install time. Bring the GPU nodes; the qualification suite will tell you
whether an untolerating pod can reach them.

### That 10.1 CPU has to fit on one worker

Read the number above as a per-node requirement, not a cluster total. Serve runs
its HTTP proxy on the head only, and the head is started with `num-cpus: 0`
because it holds the cluster's metadata rather than running work. Every one of
the thirteen applications asks for CPU, so none of them can land there. They all
go to a worker.

Four workers of 3 CPU each add up to 12 and schedule nothing, because no single
worker can hold the set. Size the worker group so that one replica has at least
**11 CPU and 28 GiB** available to Ray, and let autoscaling add replicas above
that for batch work.

Ray takes its logical CPU count from the container's limit, so requests and
limits both have to reach that number. Requests above limits is refused by the
Kubernetes API server outright, and the pod never appears at all: the RayCluster
still reports `RayClusterProvisioned: True`, because that condition counts the
pods that exist. Look at the `ReplicaFailure` condition when a worker is missing.

The applications also ask for a custom Ray resource named `serve_node`, which the
chart declares on both node groups through `rayStartParams`. If you replace the
cluster config with your own, carry that declaration across. Without it every
application stays unschedulable, the RayService sits in `Initializing` with an
empty `SERVICE STATUS`, and nothing reports an error.

### Disk, which is larger than the registry suggests

Two images land on every node that runs Mixpeek: the engine image and the
platform image that carries the API, Celery and MVS.

A registry size is compressed. What a node needs is the unpacked size, and on
our own fixture the platform image measured **4.96 GB in the registry and 14.2 GB
on disk**, a factor of 2.85. The engine image is larger still, around 15.4 GB
unpacked. Pulling the platform image took 130 seconds on a well-connected VM.

Size nodes for roughly **60 GB of image storage** before their own workload, and
expect the first pull on a cold node to take minutes rather than seconds.

## Egress on first start

An install with no route to the public internet does not come up, and the way it
fails is easy to misread as slowness.

Three of the thirteen Serve applications fetch their model weights from
`huggingface.co` the first time each replica loads: the reranker (2.2 GB), the
SigLIP image encoder (779 MB), and the MiniLM sentence encoder (88 MB). A fourth,
the CLAP audio encoder, fetches a checkpoint from the same host the first time it
sees audio. Two other models, ArcFace and the multilingual E5 encoder, are baked
into the image and need nothing.

So you have two options, and we would rather you pick one deliberately:

* **Allow egress to `huggingface.co`** from the Mixpeek namespace, at least on
  first start. Once a replica has loaded, it does not fetch again.
* **Ask us for a pre-baked image.** The engine image already bakes two of the six
  model sets by exactly this mechanism, so baking the rest is additive. The four
  together measure 4.9 GB, which is what it would add to the image.

Two applications also call hosted APIs at request time. `google__gemini_embedding_2`
embeds through Google's Generative Language and Vertex endpoints, and
`universal__api`, which backs `POST /v1/inference`, dispatches to OpenAI, Google
and Anthropic by model name. Neither is on the path for the extractors and
retrievers a standard install uses, so leaving them unreachable does not affect
indexing or search. `universal__api` also accepts a self-hosted
OpenAI-compatible endpoint, which keeps `POST /v1/inference` working with no
outbound traffic. Bring the requirement to your deployment conversation if your
environment forbids reaching a hosted model provider.

## Storage

Two different things, and it is worth separating them because they fail
differently.

**Object storage is where your index lives.** MVS keeps durable state in S3 or
GCS, not on a disk. Losing a pod does not lose the index; it loses at most the
un-replayed write-ahead window, which MVS keeps in a local `emptyDir` sized per
shard. That is a deliberate design: the vector store is object-storage-native, and
it is why a node drain is a recovery rather than a restore.

**A CSI storage class is still required, for the stateful services beside it.**
Redis runs as a StatefulSet with `volumeClaimTemplates` and needs a real
PersistentVolume. A cluster that cannot provision one cannot run the plane, even
though MVS itself would not have noticed.

* **A CSI storage class** that provisions persistent volumes surviving pod
  restarts and rescheduling. The suite writes a nonce from one pod, deletes it,
  and reads it back from a second pod on the same claim.
* **Throughput and IOPS above the floor for your workload size.** The suite
  measures sequential write and read against a stated floor and fails below it.
  It does not infer the floor from what it observed, because a bound derived from
  the measurement can never fail.
* **Object storage**, S3 or GCS, reachable from the namespace. This is the one
  MVS actually depends on, so treat it as the load-bearing requirement rather
  than the afterthought its position in this list once implied.

<Warning>
  **On EKS this is not automatic.** A stock EKS 1.31 cluster cannot provision a
  PersistentVolume at all: it ships a `gp2` StorageClass that is not marked default,
  whose provisioner is the in-tree `kubernetes.io/aws-ebs` that Kubernetes removed in
  1.31, and the EBS CSI driver is not among the default addons. It looks like working
  storage in `kubectl get storageclass` and provisions nothing. Install the
  `aws-ebs-csi-driver` addon and mark a class default before we arrive. GKE hides this
  by shipping a working default, so an EKS install is where it surfaces.
</Warning>

## A MongoDB you provide

The chart ships Redis and does not ship MongoDB. Mixpeek stores its metadata in
Mongo: objects, collections, batches, retriever definitions, and the six
control-plane collections. The API reads its connection string from the Secret you
supply at install. So a database has to exist and be reachable from the
namespace before the first install, and it is the one prerequisite on this page
that is not a property of your cluster.

* **A MongoDB the namespace can reach**, managed or self-run, inside the cluster
  or outside it. We do not pin a minimum server version: our driver is pymongo
  4.9, and what we run and test against is current MongoDB. If you are standing up
  something older, or a wire-compatible service rather than MongoDB itself, tell us
  which and we will qualify it rather than guess.
* **A connection string in the install Secret.** The API takes its whole
  environment from a Secret, so the URI goes there rather than into chart values.
* **Sized for metadata, not for media.** Documents are records and references;
  your video stays in object storage. This database stays small relative to the
  index.

If you intend the fully self-hosted shape, where nothing egresses to Mixpeek, this
database is also where the control-plane collections land. See
[the boundary](/docs/customer-hosted/data-flow#the-boundary-first) for what that
changes.

## Networking and identity

* Egress from the namespace to your object storage and to the Mixpeek control
  plane.
* In-cluster DNS and pod-to-pod routing inside the namespace. A default-deny
  `NetworkPolicy` with no allow rule for us is the common cause of a namespace
  that looks healthy and cannot talk to itself, and the suite separates that from
  a DNS failure rather than reporting one verdict for both.
* **Workload identity** if you want Mixpeek's pods to reach cloud resources
  without static credentials. GKE Workload Identity, EKS IRSA and AKS Workload
  Identity are all recognised.
* Image pull access to the Mixpeek engine image, either a pull secret in the
  namespace or a mirror into a registry your nodes already trust.

## Which of your customisations we accept

You asked whether we require customisations or direct control of the
`RayCluster`/`RayService` objects. We own those objects in the granted namespace,
and this is the honest list of what your platform's conventions can change about
them.

|                                              | Accepted                    | How                                                                                                      |
| -------------------------------------------- | --------------------------- | -------------------------------------------------------------------------------------------------------- |
| **Node selectors**                           | Yes                         | Values, per worker group. Tell us the labels and we render them rather than assuming ours                |
| **Tolerations**                              | Yes                         | Values, merged with the ones our GPU groups need                                                         |
| **Affinity / anti-affinity**                 | Yes                         | Values, rendered verbatim                                                                                |
| **priorityClassName**                        | Yes                         | Values                                                                                                   |
| **A scheduler you inject** (`schedulerName`) | Yes                         | Values                                                                                                   |
| **Extra labels and annotations**             | Yes                         | Values, on the pod template                                                                              |
| **topologySpreadConstraints**                | Yes                         | Values                                                                                                   |
| **Sidecar injection by your mesh**           | Yes, and we do not fight it | We register no admission webhooks, so your injector runs with nothing of ours in front of it             |
| **Resource requests and limits**             | Negotiated, not free        | They are sized to what the extractors actually need; too small is a Pending pod rather than a slower one |
| **Replacing our container image**            | No                          | The pinned Ray, Python and CUDA userspace is the thing that lets us upgrade without your change window   |

The mechanism is one sentence: the chart renders the `rayClusterConfig` from
values, so anything expressible in a Kubernetes pod template reaches the cluster
unmodified. There is no allowlist to extend.

<Warning>
  **One asymmetry worth knowing before you plan around it.** In Mixpeek's own
  managed deployments a generator injects scheduling into the pod spec, and there it
  **replaces** `nodeSelector` while it **merges** `tolerations`. That path is not the
  one a customer-hosted install uses, and the chart replaces nothing. We are calling
  it out because "we accept your node selectors" and "a code path exists that
  overwrites them" would both be true statements about this repo today, and you
  should not have to discover which one applies to you.
</Warning>

## What turns the chart on, and the one value that must be right

`values.yaml` is an all-off skeleton. `enabled: false` for api, celery, mvs, ray, redis
and qdrant, each template gated on its own flag, so a bare `helm install ./chart`
renders essentially nothing. What turns components on is an **install-values overlay**
applied with `-f`. Your deployment ships with an overlay written for your
environment, so the values that govern it are the ones in that file.

Two consequences worth knowing before your first install.

**A default written under a disabled component is not a default you get.** A storage
class, a node selector or a resource request sitting in `values.yaml` beneath a
component that ships off is not what your cluster will do. Your overlay is the file
that answers "what will this actually run", and it is the one to review.

<Warning>
  **`platform` is the value that must be right, because the failure is silent.** It
  keeps GKE-specific defaults when set to `gke`, and anything else turns them off. Left
  at the GKE default on EKS the install **succeeds** and the Deployments sit at 0 Ready
  with healthy containers, because `readinessGates` names
  `cloud.google.com/load-balancer-neg-ready`, which nothing outside Google satisfies.
  The other three are louder: gcsfuse volume mounts leave pods in `ContainerCreating`,
  and the `BackendConfig` and GMP `PodMonitoring` API groups do not exist off-GKE, so
  `helm install` fails outright. The readiness gate is the dangerous one precisely
  because it presents as a broken deployment rather than as a wrong value. Your
  overlay sets it for your distribution.
</Warning>

**Portability is not evenly distributed across the chart, and it is not readable from
the file names.** `api.yaml` and the four observability templates branch on `platform`.
`ray.yaml`, `mvs.yaml`, `celery.yaml`, `redis.yaml` and `qdrant.yaml` carry no such
branch. Separately, the generator that emits the Ray cluster spec writes Google node
selectors (`cloud.google.com/gke-nodepool`), so on a non-GKE cluster the node placement
in that spec is rendered data we replace for you rather than a value you override.

## Your Ray version is not an input

A Ray cluster runs one image and that image is ours. Your Ray, Python and CUDA
versions have no bearing on whether Mixpeek works, because we run our own runtime
under your scheduler rather than inside yours.

That constraint applies only to the
[shared-cluster integration](/docs/customer-hosted/existing-raycluster), which is scoped
per cluster.

## What stays yours

Node pools, quotas, autoscaler policy, taints and network policy. The
[qualification suite](/docs/customer-hosted/qualification) tests that they stay
authoritative: work that must land on your designated pool, work that must be
refused for want of a toleration, and work your quota must reject.

## Teardown

Removing the kit's objects removes what Mixpeek created. The suite's
`--expect-clean` mode runs afterwards and fails if residue remains, so the claim
gets checked.
