The live Milvus servers of database-compose.yml, the one-shots that seed them and generate their certificates, and the scripts those one-shots run.
The vector family's captures (tests/fixtures/vector/), the Milvus provider's captures and live check, and every hand pass run against these servers, so each seeded object below exists for a rule and says which.
| Service | Profile | From the host | Transport and authentication |
|---|---|---|---|
milvus |
none | 127.0.0.1:19530 (gRPC and REST), 127.0.0.1:19091 (the management port 9091) |
Plaintext, authorization on |
milvus-tls |
milvus-tls |
127.0.0.1:19531 (gRPC over TLS), 127.0.0.1:19541 (REST over TLS) |
One-way TLS (tlsMode 1), authorization on |
milvus-mtls |
milvus-tls |
127.0.0.1:19532 (gRPC over mutual TLS) |
Mutual TLS (tlsMode 2), authorization on |
The one-shots are milvus-seed, which seeds milvus, and milvus-certs, which generates the TLS servers' certificates.
Every server runs milvusdb/milvus:v3.0.2, pinned by the digest sha256:5f13bf88e110a517911c3e6dd8172454e90042c21e606a868084615a4302c8a0.
The one-shots run python:3.12-slim and alpine/openssl:3.5.8, each pinned by digest in the compose file.
Each server follows Milvus's standalone_embed.sh at tag v3.0.2: seccomp unconfined, the embedded etcd, local storage, DEPLOY_MODE=STANDALONE, the embedEtcd.yaml and user.yaml beside this file, the /healthz probe and milvus run standalone.
Three changes were measured before they were made.
Each server keeps its data in a named volume of its own, because the image runs as uid 999 and the script's bind mount crashed it with exit code 134.
The image is v3.0.2, where the script at that tag pins v3.0.1.
The TLS servers serve REST on a listener of their own (PROXY_HTTP_PORT 8080), because TLS forces separate ports.
The embedded etcd's 2379 is never published, and the write-ahead log is RocksMQ.
Every port is bound to a loopback address only, and each server is bounded to 2 CPUs and 4 GiB, against 156 to 292 MiB measured after seeding.
The restart policy is unless-stopped: during the research the embedded etcd of one server stalled on fdatasync for 7.8 seconds, lost its lease and exited with code 80.
A harness checks every service's health first and fails loudly on an exited or unhealthy server, with no retry.
No 2.6 server is here, because the provider claims Milvus 3.0 alone.
On 3.0.2 the management port 9091 answers without authentication, its state-changing paths included.
It is published on loopback as 19091 for the live check's telemetry read alone, and the provider never dials it.
Keep it off every network: never publish it on another address, and never forward it.
Host port 9091 is prometheus-auth's.
Name the compose project and every service, as every command here does.
up with no service names starts every server in database-compose.yml, and the project name keeps one set of volumes when a command runs from a git worktree.
Start a server with up --wait, which returns once it is healthy, and a one-shot with a plain up -d, then read its exit code with docker wait, which must print 0.
docker compose -p libredb-studio -f database-compose.yml up -d --wait milvus
docker compose -p libredb-studio -f database-compose.yml up -d milvus-seed
docker wait libredb-milvus-seed
docker compose -p libredb-studio -f database-compose.yml --profile milvus-tls up -d milvus-certs
docker wait libredb-milvus-certs
docker compose -p libredb-studio -f database-compose.yml --profile milvus-tls up -d --wait \
milvus-tls milvus-mtlsThe first seed installs its pinned packages and takes about two minutes; a second run on a seeded server creates nothing. To read a seeded server back without writing to it, and to print the manifest the vector fixtures record:
docker compose -p libredb-studio -f database-compose.yml run --rm --no-deps -T milvus-seed \
--uri http://milvus:19530 --verify
docker compose -p libredb-studio -f database-compose.yml run --rm --no-deps -T milvus-seed \
--uri http://milvus:19530 --manifest --image "$(docker inspect --format '{{.Config.Image}}' libredb-milvus)"--manifest requires --image, the server's reference pinned by digest, and records that image, its digest and the date it printed the manifest.
To start one fixture over, remove its containers by name, then its volumes, whose names carry the project's prefix:
docker compose -p libredb-studio -f database-compose.yml --profile milvus-tls rm -s -f \
milvus milvus-seed milvus-certs milvus-tls milvus-mtls
docker volume rm libredb-studio_milvus-data libredb-studio_milvus-tls-data libredb-studio_milvus-mtls-data \
libredb-studio_milvus-certs libredb-studio_milvus-credentialsseed.pycreates every object below that is missing and leaves what exists, so a second run does no work;--verifyreads every object back and exits 1 on a difference, and--manifestprints, as JSON, each collection's declaration, its row count and its first five rows byseqwith every vector as the server stores it. The data is deterministic, from fixed numpy seeds, apart from the keys the server assigns todocs_int64, so every fixture addresses rows byseq.certs.sh <directory>generates the TLS servers' certificates into themilvus-certsvolume at first start, so no private key is ever committed.requirements.inpins every package the seed installs, the versions the research seed's environment held: pymilvus 3.0.2, numpy 2.5.3, ml_dtypes 0.6.0 and their dependencies.requirements.txtadds the sha256 of each one'slinux/amd64wheel; it is generated bysh docker/lock-requirements.sh docker/milvus <the seed's image>and never edited by hand. Somilvus-seeddeclaresplatform: linux/amd64, and an arm64 host runs it under emulation instead of failing on the hashes.embedEtcd.yamlanduser.yamlare the two filesstandalone_embed.shwrites, unchanged.
| User | Role | Password |
|---|---|---|
root |
the built-in admin | Milvus's documented default, Milvus: the server's own default, which nothing here sets, and which the provider warns about |
reader |
reader_role: CollectionReadOnly and DatabaseReadOnly on default/* |
reader.password in the milvus-credentials volume |
nobody |
none | nobody.password in the milvus-credentials volume |
The passwords are random, 32 hexadecimal characters each, generated by the first seed.
A token is user:password, sent as authorization metadata over gRPC and as Authorization: Bearer over REST.
Copy the passwords out of the seed's container into a directory outside the repository, and never commit them.
mktemp -d makes a fresh directory only you can read, so the copy never lands in an earlier one or in a path another user can read:
dir=$(mktemp -d)
docker cp libredb-milvus-seed:/credentials "$dir/credentials"| Collection | What it holds | The rule it serves |
|---|---|---|
docs_int64 |
2,000 rows: an Int64 key the server assigns (above 2^53), seq, an 8-dimension FloatVector (HNSW, COSINE), VarChar, JSON, an Int64 array, a nullable Int32, a dynamic field holding an integer above 2^53 on every tenth row; partitions part_a and part_b |
keys above 2^53 assigned by the server, rows addressed by seq; partitions; JSON, array and null cells |
docs_varchar |
500 rows: a VarChar key and every vector type: Float16Vector (L2), BFloat16Vector (IP), a 16-bit BinaryVector (HAMMING), SparseFloatVector (IP), Int8Vector (L2) |
a cell and a search of every vector type and of four metrics |
fts |
200 short English sentences and their BM25 function's sparse output | a function's output field, searched with text |
unloaded_big |
50,000 rows of 128 dimensions (IVF_FLAT, L2), released |
a search refused as not loaded; Load with its preview on a copy the live check owns |
scratch |
nothing at seed time; a dynamic field | the one writable collection, compared on schema, configuration, aliases and load state only |
edge_values |
five rows: every vector type at its range ends (float32 3.4028235e38, -1e-45 and 1.1754944e-38, float16 65504 and 2^-24, the bfloat16 maximum, int8 -128 and 127, binary all 0x00 and all 0xff, a sparse index 4294967294), a sparse value of 3.4e38, and (3,4) | the copy loop's edge rows; the non-finite score of the 3.4e38 row's self-search; (3,4) against the origin answering 25.0, because L2 is squared |
pk_partitioned |
2,000 rows with a partition key over 1,024 partitions | partitions/list past the row cap; partitionNames refused on a partition-key collection |
large_topk |
100 rows; the property query_mode is large_topk |
large_topk detected by its exact property |
shadowed |
six rows: four written while zeta was a dynamic key, then AddCollectionField added a static zeta, then two more |
the merge rule's live collision of a static and a dynamic key |
wide_768 |
1,000 normalised embeddings of 768 dimensions | the result byte budget |
emb_list |
three rows, each with a 4-dimension FloatVector (L2) and a struct array chunks of one to three elements, whose FloatVector subfield chunks[emb] is an embedding list (HNSW, MAX_SIM_COSINE) |
an embedding list's multivector cells and a MAX_SIM search |
probe_db.notes |
100 rows in a second database | a database other than default, which reader may not read |
Every collection but unloaded_big is loaded.
reader lists only the database default and reads every collection in it; nobody lists nothing.
certs.sh generates a CA (ca.pem), one server certificate for both TLS servers (server.pem, server.key), naming localhost, 127.0.0.1, milvus-tls and milvus-mtls, with the extended key usage serverAuth, and the client certificates mutual TLS is measured with:
| Files | What milvus-mtls does with it |
|---|---|
client.pem, client.key |
accepts it: clientAuth, issued by the fixture's CA |
client-serverauth.pem, client-serverauth.key |
refuses it: issued for serverAuth only |
client-expired.pem, client-expired.key |
refuses it: expired on 2025-01-02 |
client.pem, client-mismatched.key |
a key that does not match the certificate, which a client refuses before it dials |
Every file is mode 644, because the server runs as uid 999. Copy them out of the one-shot's container into a fresh directory only you can read, outside the repository:
dir=$(mktemp -d)
docker cp libredb-milvus-certs:/certs "$dir/certs"