New to KubeDB? Please start here.
Prepare Milvus Dependencies
Milvus will not start from a bare Milvus manifest alone. Every Milvus deployment in KubeDB needs:
- Object storage, exposed through a secret (one per Milvus instance;
milvus-storage-configandmilvus-cluster-storage-configin these guides). - etcd for metadata.
This guide sets up object storage in the demo namespace and explains the two etcd options: an internal etcd that KubeDB creates for you (the default), or an external etcd cluster you manage yourself.
Before You Begin
You need a Kubernetes cluster and
kubectlconfigured to talk to it.Install KubeDB with the Milvus and Etcd feature gates enabled. The internal metadata store that KubeDB creates for each Milvus is a KubeDB
Etcdobject, so the Etcd feature gate is required too:helm install kubedb oci://ghcr.io/appscode-charts/kubedb \ --namespace kubedb --create-namespace \ --set global.featureGates.Milvus=true \ --set global.featureGates.Etcd=true
Create the Demo Namespace
All Milvus examples in this guide use the demo namespace:
$ kubectl create namespace demo
namespace/demo created
Install MinIO
Milvus stores its segments and logs in object storage. We recommend using the MinIO Operator to deploy a MinIO Tenant.
1. Install the MinIO Operator
$ helm repo add minio https://operator.min.io/
$ helm repo update minio
$ helm upgrade --install --namespace "minio-operator" --create-namespace "minio-operator" minio/operator --set operator.replicaCount=1
2. Deploy a MinIO Tenant
The tenant chart’s default image (quay.io/minio/minio) can no longer be pulled anonymously (the pull fails with 401 UNAUTHORIZED and the pod stays in ImagePullBackOff), so the command below overrides it with a pullable MinIO image:
$ helm upgrade --install --namespace "demo" milvus-minio minio/tenant \
--set tenant.name=milvus-minio \
--set tenant.image.repository=docker.io/bitnamilegacy/minio \
--set tenant.image.tag=2025.7.23-debian-12-r3 \
--set tenant.pools[0].servers=1 \
--set tenant.pools[0].volumesPerServer=1 \
--set tenant.pools[0].size=1Gi \
--set tenant.certificate.requestAutoCert=false \
--set tenant.buckets[0].name="mlv-release" \
--set tenant.pools[0].name="default"
The MinIO Operator allows only one Tenant per namespace. If the namespace already has a Tenant, reuse it as described in If You Already Have S3 or MinIO. Also note that
tenant.namedoes not rename the chart’s config secret (myminio-env-configuration), so a second tenant release in the same namespace collides with the first one.
Wait for the Tenant to become initialized:
$ kubectl get tenant -n demo milvus-minio -w
NAME STATE HEALTH AGE
milvus-minio Initialized green 1m
3. Create the Storage Config Secrets
Create a secret with your MinIO connection details. The keys must use the milvus.yaml config key names directly (KubeDB copies them into the minio section of milvus.yaml).
Use a distinct rootPath for every Milvus instance. rootPath is the prefix under which a Milvus stores all of its object-storage data (segments, logs and its write-ahead log). Two Milvus instances on the same bucket and rootPath read and write the same objects: one instance’s garbage collection can delete the other’s files, and their write-ahead-log lock objects collide. A secret holds one rootPath, so create one secret per Milvus instance.
Deleting a Milvus (with any deletionPolicy) does not delete its objects under <bucketName>/<rootPath>/. Reusing that rootPath for a new Milvus is not supported: the new instance gets a new metadata etcd, collides with the old write-ahead-log lock objects under <rootPath>/wp/, and can hang in Provisioning with writer lock session has expired in its logs. Before you create a new database on a rootPath that was used before, either change rootPath in its secret or delete the old prefix first.
The guides deploy a standalone Milvus named milvus-standalone, which uses milvus-storage-config, and a distributed Milvus named milvus-cluster, which uses milvus-cluster-storage-config. Both can run at the same time:
apiVersion: v1
kind: Secret
metadata:
name: milvus-storage-config
namespace: demo
type: Opaque
stringData:
address: "milvus-minio-hl.demo.svc.cluster.local"
accessKeyID: "minio"
secretAccessKey: "minio123"
bucketName: "mlv-release"
rootPath: "milvus-standalone"
port: "9000"
---
apiVersion: v1
kind: Secret
metadata:
name: milvus-cluster-storage-config
namespace: demo
type: Opaque
stringData:
address: "milvus-minio-hl.demo.svc.cluster.local"
accessKeyID: "minio"
secretAccessKey: "minio123"
bucketName: "mlv-release"
rootPath: "milvus-cluster"
port: "9000"
Apply the secrets:
$ kubectl apply -f milvus-storage-config.yaml
secret/milvus-storage-config created
secret/milvus-cluster-storage-config created
Verify:
$ kubectl get secret -n demo milvus-storage-config milvus-cluster-storage-config
NAME TYPE DATA AGE
milvus-storage-config Opaque 6 1m
milvus-cluster-storage-config Opaque 6 1m
If You Already Have S3 or MinIO
You do not need to use the MinIO Operator. You can instead create only the secrets and point Milvus at your existing object store (again, one secret with its own rootPath per Milvus instance):
apiVersion: v1
kind: Secret
metadata:
name: milvus-storage-config
namespace: demo
type: Opaque
stringData:
address: "existing-minio.demo.svc.cluster.local"
accessKeyID: "minioadmin"
secretAccessKey: "minioadmin"
bucketName: "milvus"
rootPath: "files-1"
port: "9000"
If you use a different secret name, update spec.objectStorage.configSecret.name in the Milvus manifest accordingly.
etcd for Metadata
Milvus always uses etcd as its metadata store. In KubeDB, there are two supported patterns:
- KubeDB-managed etcd: omit
spec.metaStorage. KubeDB creates an internal etcd for you as a KubeDBEtcdobject. This needs theEtcdfeature gate (--set global.featureGates.Etcd=true) in the KubeDB install; no separate etcd operator is needed. - Externally managed etcd: set
spec.metaStorage.externallyManaged: trueand provide endpoints yourself.
Verify that the KubeDB Etcd CRD is registered:
$ kubectl get crd etcds.kubedb.com
Default Path: KubeDB-Managed etcd
For the standalone and distributed quickstarts, this is the default and recommended path.
If you omit spec.metaStorage from the Milvus manifest:
- KubeDB creates an internal
Etcdobject named<milvus-name>-etcd(3 members) and an auth secret<milvus-name>-etcd-authfor it - KubeDB wires Milvus to that internal etcd automatically
- You do not need to apply any etcd YAML yourself
So for the default quickstarts, the Etcd feature gate in the KubeDB install is enough.
Optional Path: Use External etcd
If you already manage etcd yourself, do not let KubeDB create an internal metadata cluster. Instead, set spec.metaStorage.externallyManaged: true and provide your own etcd endpoints:
metaStorage:
externallyManaged: true
endpoints:
- http://etcd-0.example.svc.cluster.local:2379
- http://etcd-1.example.svc.cluster.local:2379
- http://etcd-2.example.svc.cluster.local:2379
Requirements for external etcd:
- The endpoints must be reachable from the Milvus pods.
- The etcd cluster must already be healthy before you create the
Milvusobject. - If the external etcd requires authentication or TLS, reference its credentials with
spec.metaStorage.authSecretand its client certificate withspec.metaStorage.clientTLSSecret(see the Milvus concepts).
Only choose this path if you intentionally want Milvus to use an external etcd cluster. The default quickstarts do not require any external etcd manifest.
Optional Controllers
These are not required for the base quickstarts:
- Install Prometheus Operator only if you want to follow the monitoring guide.
- Install cert-manager only if you want to follow the TLS guide or the TLS reconfiguration guide.
Cleanup
If you delete the whole demo namespace, Kubernetes removes the Milvus, MinIO, and etcd resources in that namespace together:
$ kubectl delete namespace demo
namespace "demo" deleted
If you want to keep the namespace and clean up dependencies separately:
Delete your Milvus objects first (see the cleanup section of each guide). With
deletionPolicy: DeleteorWipeOut, KubeDB also deletes the internal etcd and its PVCs.Delete what the internal etcd leaves behind. The
<milvus-name>-etcd-authsecret is kept even after aWipeOutdelete. If any etcd PVCs remain (for example, afterdeletionPolicy: Halt), delete them too. Their names follow the patterndata-<milvus-name>-etcd-<ordinal>:$ kubectl delete pvc -n demo data-milvus-standalone-etcd-0 data-milvus-standalone-etcd-1 data-milvus-standalone-etcd-2 $ kubectl delete secret -n demo milvus-standalone-etcd-authIf you use an external etcd, clean it up with whatever workflow manages it.
Delete each Milvus instance’s object-storage data, as shown in Delete a Milvus Instance’s Object-Storage Data.
Delete the MinIO Tenant:
$ helm uninstall milvus-minio -n demoDelete the storage config secrets:
$ kubectl delete secret -n demo milvus-storage-config milvus-cluster-storage-config
Delete a Milvus Instance’s Object-Storage Data
KubeDB does not delete a Milvus instance’s objects from object storage, even with deletionPolicy: WipeOut. Once the Milvus object is gone, remove its rootPath prefix with the MinIO client (mc) before reusing that rootPath for a new Milvus. For the tenant created above, port-forward the MinIO service in a separate terminal:
$ kubectl port-forward svc/milvus-minio-hl -n demo 9000:9000
Then register an alias and delete the prefix of the deleted database, milvus-standalone in this example:
$ mc alias set milvus-minio http://localhost:9000 minio minio123
$ mc rm -r --force milvus-minio/mlv-release/milvus-standalone
For your own object store, use its endpoint, credentials, bucketName and rootPath instead (mc rm -r --force <alias>/<bucket>/<rootPath>).
































