
Containerized AI pipelines need somewhere to keep their embeddings, and the choice of vector store quietly shapes the rest of the stack. Milvus, Qdrant, and Weaviate all run under Docker Compose, but each arrives with a different container topology, a different API surface, and a different story about what happens when the data outgrows one node. Milvus deploys as a miniature distributed system even in standalone mode, Qdrant ships as a single Rust binary with dual REST and gRPC ports, and Weaviate pairs a modular core with optional inference containers for its text2vec modules. This comparison examines what each engine looks like in a compose file, how its APIs are exposed, and how its scaling path evolves from a single host toward a cluster. The aim is to give solo engineers and small teams a concrete basis for choosing an engine by operational footprint rather than feature checklists.

| Engine | Services in a minimal docker‑compose.yml | Ports exposed | Primary health‑check |
|---|---|---|---|
| Milvus (standalone) | etcd, minio, milvus – three containers that start before any client code is added. The etcd container runs the CoreOS image quay.io/coreos/etcd:v3.5.x, the object‑store runs minio/minio (S3‑compatible), and the Milvus service runs milvusdb/milvus with the command milvus run standalone【0†content】. | 19530 (gRPC API) – Milvus<br>9091 (health/metrics) – Milvus<br>9000 (MinIO API)<br>9001 (MinIO console)【0†content】 | etcd uses an etcdctl endpoint health command in its healthcheck stanza – a documented pattern for determining readiness【0†content】. |
| Qdrant | A single binary container from qdrant/qdrant (no auxiliary services)【1†content】. | 6333 (REST)<br>6334 (gRPC) – both can be overridden via QDRANT__SERVICE__GRPC_PORT【1†content】. | No built‑in health‑check; most deployments rely on Docker’s default “healthy” status (container exits on fatal errors). A simple TCP probe against 6333 can be added manually. |
| Weaviate | Core container cr.weaviate.io/semitechnologies/weaviate:<version> (e.g., 1.39.2)【4†content】; optionally a second transformers-inference container when the text2vec‑transformers module is enabled【4†content】. | 8080 (HTTP)<br>50051 (gRPC) – default mapping for both containers【4†content】. | Weaviate starts a /v1/.well-known/ready endpoint that returns 200 when the DB is ready; the Docker image marks the container as healthy once this endpoint responds. |
docker compose ps snapshotsdepends_on) forces etcd → minio → milvus startup order, so a restart of any service briefly blocks the whole stack.etcd, minio, milvus directories) and ensure the health of each before proceeding. Qdrant needs only the /qdrant/storage volume, while Weaviate requires the PERSISTENCE_DATA_PATH directory and, if used, the inference container’s model cache./etcd, /minio_data, /var/lib/milvus) is larger than Qdrant’s single qdrant_storage or Weaviate’s ./data. Larger volume trees increase backup time and storage‑cost calculations for solo engineers.quay.io/coreos/etcd and minio/minio images, Milvus’ own milvusdb/milvus tag, Qdrant’s qdrant/qdrant, and Weaviate’s cr.weaviate.io/semitechnologies/weaviate registry path have all moved across releases. Pinning exact tags (e.g., milvusdb/milvus:v2.5.27-gpu, qdrant/qdrant:1.5.0, weaviate:1.39.2) prevents accidental API or health‑check changes.Bottom line: The first operational cost you pay is the number of containers you must orchestrate, monitor, and back up. Milvus adds two supporting services even in “standalone” mode, Qdrant stays on a single binary, and Weaviate stays single‑service unless you need local transformer inference. Weigh this container count against feature requirements before scaling out to a cluster.

Qdrant can be launched with a single docker run (or a single Compose service) and works out‑of‑the‑box – no extra services, no cluster manager, and both its REST (6333) and gRPC (6334) endpoints are exposed by default【1】. Persistence is achieved by mounting a host directory to /qdrant/storage; the container will write all segment files, write‑ahead‑log (WAL) entries, and metadata there【1】.
| Area | How to set | Typical values |
|---|---|---|
| Storage | QDRANT__STORAGE__STORAGE_PATH or a mounted production.yaml at /qdrant/config/production.yaml | /qdrant/storage |
Optimizers (e.g., default_segment_number, memmap_threshold) | default_segment_number: 2, memmap_threshold: 20000 | |
| Crash‑recovery | write_ahead_log: true | |
| Service | QDRANT__SERVICE__GRPC_PORT (default 6334) | grpc_port: 6334 |
| HTTP port | http_port: 6333 | |
| Telemetry | telemetry.enabled | false to disable data collection |
A full YAML example (mounted as /qdrant/config/production.yaml) follows the structure above【2】.
Qdrant stores arbitrary JSON payloads alongside vectors. When a query includes filter clauses (must, should, must_not), the filter is evaluated during HNSW graph traversal, not as a post‑processing step. This design keeps filtered queries as fast as unfiltered ones【5】.
version: "3.8"
services:
qdrant:
image: qdrant/qdrant:latest
container_name: qdrant
ports:
- "6333:6333" # REST API
- "6334:6334" # gRPC API
volumes:
- qdrant_data:/qdrant/storage
environment:
QDRANT__SERVICE__GRPC_PORT: 6334
deploy:
resources:
limits:
memory: 2G # example limit from official docs
restart: always
volumes:
qdrant_data:
After docker compose up -d, a quick smoke test can verify the service:
curl -s http://localhost:6333/collections | jq .
memmap_threshold and other optimizer defaults may vary between releases; pin the container tag and compare the version’s defaults before tuning【2】.For solo developers or small teams, Qdrant offers the lowest‑friction entry point: a single container, dual‑protocol access, and straightforward persistent storage. Its built‑in filtering efficiency and rich client ecosystem make it a solid default choice when the operational footprint is the primary selection criterion.

Milvus’ “stand‑alone” Docker Compose profile already contains three containers: the Milvus service itself, an etcd container for metadata, and a MinIO container for object storage. The compose snippet typically looks like this:
version: '3.8'
services:
etcd:
image: quay.io/coreos/etcd:v3.5.5
...
minio:
image: minio/minio:RELEASE.2023-03-13T19-46-17Z
...
milvus:
image: milvusdb/milvus:v2.5.27
command: ["milvus", "run", "standalone"]
environment:
ETCD_ENDPOINTS: etcd:2379
MINIO_ADDRESS: minio:9000
Ports – Clients talk to Milvus over gRPC on 19530 (used by PyMilvus, the Java SDK, and the Node.js SDK @zilliz/milvus2-sdk-node). A health and metrics endpoint is exposed on 9091 (/healthz) source. MinIO’s S3 API runs on 9000 and its console on 9001.
Resource footprint – Production‑grade deployments are advised to allocate 16 GB + RAM; smaller amounts suffice only for testing or very tiny collections source.
The stand‑alone mode collapses Milvus’ native micro‑service architecture into the three containers above, but the underlying design remains distributed. Moving to a true cluster introduces the following roles:
| Component | Function |
|---|---|
| Root coordinator | Global metadata and task orchestration |
| Data node | Handles data ingestion and segment storage |
| Query node | Serves ANN search requests |
| Index node | Builds and updates indexes (CPU‑ or GPU‑accelerated) |
| Proxy | Front‑ends client traffic and load‑balances among query nodes |
| etcd quorum | Metadata replication and leader election |
Even a “lite” cluster typically involves 7–10 services, and production clusters are usually orchestrated with Helm on Kubernetes source. The etcd quorum becomes a critical dependency: loss of a majority can stall the entire deployment.
Milvus 2.5 introduced GPU‑accelerated ANN indexes—CAGRA and GPU‑IVF—making Milvus the only engine among Milvus, Qdrant, and Weaviate that can run ANN search on a GPU. When enabled, the server’s VRAM consumption sits around 2–4 GB source.
Milvus 2.6 replaced its earlier Kafka/Pulsar‑based write‑ahead log with Woodpecker, a WAL built directly on object storage, simplifying the dependency graph and reducing external components source.
Milvus ships with Attu, an official web‑based GUI that lets users create collections, inspect indexes, and run ad‑hoc queries. Attu alleviates some of the operational complexity introduced by the etcd/MinIO chain source.
In short, Milvus trades a heavier operational footprint—etcd, MinIO, and higher memory requirements—for the ability to scale to massive vector collections and to leverage GPU‑accelerated ANN search. The compose file is the “entry ticket” to that scaling story, while Attu offers a modest UI to tame the inherent complexity.

The core Weaviate container is a single service that listens on 8080 for HTTP/GraphQL and 50051 for gRPC traffic【0†content】. All runtime options are supplied through environment variables, keeping the docker‑compose.yml declarative and portable. The most commonly used flags are:
QUERY_DEFAULTS_LIMIT – default result size for queries.AUTHENTICATION_ANONYMOUS_ACCESS_ENABLED – toggles anonymous access (useful for local development).PERSISTENCE_DATA_PATH – directory inside the container where persisted data lives.DEFAULT_VECTORIZER_MODULE – selects the default vectorizer (e.g., text2vec-transformers).ENABLE_MODULES – comma‑separated list of active modules.CLUSTER_HOSTNAME – identifier of the node; the docs recommend setting it even for single‑node deployments to avoid hostname‑drift failures【0†content】.Vectorization is not baked into the core binary. Instead, each vectorizer runs in its own container and is referenced by the core through the TRANSFORMERS_INFERENCE_API variable. A typical two‑service compose file looks like this:
services:
weaviate:
image: cr.weaviate.io/semitechnologies/weaviate:1.39.2
ports: [ "8080:8080", "50051:50051" ]
environment:
QUERY_DEFAULTS_LIMIT: 25
AUTHENTICATION_ANONYMOUS_ACCESS_ENABLED: "true"
PERSISTENCE_DATA_PATH: /var/lib/weaviate
DEFAULT_VECTORIZER_MODULE: text2vec-transformers
ENABLE_MODULES: text2vec-transformers
TRANSFORMERS_INFERENCE_API: http://text2vec-transformers:8080
CLUSTER_HOSTNAME: node1
text2vec-transformers:
image: cr.weaviate.io/semitechnologies/transformers-inference:sentence-transformers-multi-qa-MiniLM-L6-cos-v1
environment:
ENABLE_CUDA: 0 # set to 1 on GPU‑enabled hosts
The inference container hosts a Hugging Face transformer (e.g., sentence‑transformers/multi‑qa‑MiniLM‑L6‑cos‑v1) and can optionally use CUDA for GPU acceleration【1†content】. Because the model runs as a separate process, memory consumption mirrors the model size rather than the core database footprint.
Weaviate can also be run without any modules, accepting pre‑computed vectors directly from downstream pipelines. This mode is useful when the application already handles embedding generation or when a lightweight footprint is required【2†content】.
One of Weaviate’s distinctive features is native hybrid retrieval, which blends BM25 keyword scoring with dense‑vector similarity in a single request. The alpha parameter controls the weighting:
alpha = 0 → pure BM25 (lexical).alpha = 1 → pure vector (semantic).0.5) give a balanced mix.BM25 tuning parameters (bm25_k1, bm25_b) are also configurable per class, allowing fine‑grained control of lexical relevance【3†content】. The fusion happens inside the engine, eliminating the need for a separate keyword index or post‑processing step【4†content】.

Qdrant expands a single‑binary deployment into a distributed cluster through Raft consensus. Every node participates in the same log, guaranteeing a consistent view of collections and shard locations. By default the system creates one shard per node, but the documentation recommends starting with 12 shards so the topology can grow from 1 → 2 → 3 → 6 → 12 nodes without needing to reshard, at the cost of reduced throughput on small clusters【https://qdrant.tech/documentation/overview/】. Production‑grade setups should use a replication factor ≥ 2 to survive a node loss and enable zero‑downtime rolling updates【https://qdrant.tech/documentation/overview/】. The Raft leader handles metadata changes, while followers keep full copies of each shard, making the failure mode easy to reason about for a solo operator.
Weaviate relies on a gossip‑based ring for intra‑cluster discovery and Raft for leader election. The compose file makes the cluster topology explicit: each service sets CLUSTER_HOSTNAME, CLUSTER_GOSSIP_BIND_PORT (default 7100), CLUSTER_DATA_BIND_PORT (typically 7101), RAFT_JOIN with the list of members, and RAFT_BOOTSTRAP_EXPECT equal to the number of voters. A three‑node sample is provided in the official Docker‑Compose guide and can be used for local replication testing, with one node designated as the founding member【https://docs.weaviate.io/deploy/installation-guides/docker-installation】. Because the gossip and Raft ports are exposed directly, you can spin up a full multi‑node cluster on a single host for experimentation, then promote the same configuration to a proper Kubernetes deployment.
Milvus moves beyond Docker‑Compose once you need horizontal scaling. Even the “standalone” mode bundles several containers (etcd, MinIO, Milvus service) and the true cluster mode introduces distinct node types—query, data, index, and coordinator—plus an etcd quorum for metadata and object storage for persisted vectors【https://ramnode.com/guides/milvus】. The architecture is intentionally micro‑service‑oriented; scaling out requires the Milvus Kubernetes operator, which orchestrates the various pod types and handles sharding, replication, and WAL coordination【https://dev.to/krunalkanojiya/pinecone-vs-weaviate-vs-milvus-vs-qdrant-which-vector-db-in-2026-26dc】. Consequently, a solo engineer who wishes to stay within Docker‑Compose will hit a hard ceiling with Milvus, whereas Qdrant or Weaviate can be prototyped and debugged locally before moving to a larger platform.
All cluster‑related APIs—shard creation, RAFT_JOIN, or node‑type registrations—are version‑sensitive and may evolve between releases. When choosing an engine, consider which two‑node failure mode you can realistically troubleshoot alone; Qdrant’s Raft logs and Weaviate’s gossip diagnostics are generally more approachable than Milvus’s multi‑service etcd quorum.
| Engine | Typical RAM (stand‑alone) | GPU / VRAM (when used) | Notable runtime characteristics |
|---|---|---|---|
| Qdrant | ≈ 512 MB RAM for a corpus comparable to Weaviate’s benchmark[^1] | – (CPU‑only ANN) | Rust implementation has no garbage collector, eliminating GC pauses. |
| Weaviate | ≈ 768 MB RAM for the same workload[^1] | ≈ 500 MB VRAM for the core; additional GPU memory if transformer modules are enabled[^5] | Go’s garbage collector can introduce brief pauses under high throughput, though they are generally smaller than embedding latency[^1]. |
| Milvus | 4 GB RAM for small dev datasets; 16 GB+ recommended for production workloads[^6] | 2–4 GB VRAM for GPU‑accelerated ANN indexes (CAGRA, GPU‑IVF)[^4] | Largest server footprint among the three; GPU support drives the VRAM requirement. |
Qdrant supports scalar, product, binary (available since ~v1.8)[^2][^3], TurboQuant (added in v1.18) and can compress vectors up to 32× (≈ 97 % RAM reduction) while delivering up to 40× faster search for high‑dimensional data[^7][^8]. A worked example shows that 50 M × 1536‑dim float‑32 embeddings (~300 GB uncompressed) can be stored under 10 GB with binary quantization on a 16 GB machine, at the cost of modest recall loss[^2][^3].
Weaviate offers rotational, scalar, binary, product, and MUVERA quantization methods[^9]. Its multi‑tenancy model can offload inactive tenants to cheap object storage, allowing ≈ 18–19 k active tenants per node in the authors’ internal tests[^10].
Milvus provides scalar and product quantization; binary quantization is less mature and not a primary focus of its current releases[^2][^3].
In a full Dify‑stack deployment, the Weaviate container consumes about 900 MB idle RAM[^5]. The total stack runs comfortably on an 8 GB VPS, but a 2 GB‑4 GB environment quickly becomes constrained once query traffic spikes.
Qdrant’s lean binary (single Rust process) typically stays under 512 MB RAM when idle, thanks to its small resident set and configurable caching policies[^1][^10].
Milvus, even without GPU indexing, reserves ≈ 200 MB RAM for the core process, but the GPU‑enabled variant adds several gigabytes of VRAM usage as shown above[^4].
Before selecting an engine, model the total memory requirement as:
vector_count × dimensions × bytes_per_dimension
Then factor in the quantization factor (e.g., 1/32 for binary/TurboQuant) and the runtime overhead of the chosen container. Because RAM, VRAM, and latency figures vary with hardware, workload patterns, and version releases, treat all numbers here as hardware‑, workload‑, and version‑dependent. Quantization can dramatically shrink the hardware budget, but it may also affect recall and throughput, influencing the overall engine choice.

| Engine | Primary API(s) | Default ports* | Official client libraries | Built‑in UI |
|---|---|---|---|---|
| Qdrant | REST (OpenAPI 3.0) + gRPC | 6333 (REST) / 6334 (gRPC) | Python, JavaScript/TypeScript, Rust, Go, .NET, Java — six official clients【{"cursor": 0, "id": 0}】 | Web UI baked into the Docker image (often described as “cleaner for day‑to‑day development”)【{"cursor": 2, "id": 0}】 |
| Weaviate | HTTP / GraphQL + gRPC (via grpc- sub‑domain) | 8080 (HTTP/GraphQL) / 50051 (gRPC) | Python, Go, Java, .NET, Node.js, Rust (official SDKs) – see Weaviate docs for full list【{"cursor": 1, "id": 0}】 | No dedicated UI; admin UI is a separate “Weaviate Console” container (optional) |
| Milvus | gRPC‑first | 19530 (gRPC) / 9091 (HTTP health & metrics) | Python, Go, Java, Node.js, C++, (and others)【{"cursor": 0, "id": 4}】 | Attu – the official web GUI for collection management and index monitoring【{"cursor": 2, "id": 0}】 |
*Ports are the defaults for the official Docker images; they may change between releases.
curl command, which is handy for solo developers or CI checks./v1 API, which can reduce boilerplate when the client already speaks GraphQL.| Engine | Container topology (stand‑alone) | Scaling path |
|---|---|---|
| Qdrant | Single Rust binary; one container runs both REST and gRPC ports. | Add more identical containers behind a load balancer; no external services (etcd, object store) required. |
| Weaviate | Core container + optional text2vec‑transformers module (GPU‑enabled) as a separate container. | Horizontal scaling adds more core + module pairs; the module container can be swapped for a different vectorizer without touching the core. |
| Milvus | Minimum three containers: Milvus service, etcd, and MinIO (object storage). | Production clusters use the Milvus Operator or Helm charts to spin up many replicas of each component, exposing a multi‑service mesh. This “mini‑distributed system” adds operational overhead but enables true sharding and fault tolerance. |
The Spheron guide distills the three engines into clear decision rules【{"cursor": 5, "id": 0}】:
| Use‑case | Preferred engine | Why |
|---|---|---|
| GPU‑accelerated ANN search on a corpus of 1 M – 100 M vectors (e.g., semantic search with sub‑millisecond latency) | Milvus (CAGRA / GPU‑IVF) | Only Milvus ships native CAGRA GPU indexes, delivering 10–50× faster builds and sub‑ms query latency on CUDA GPUs【{"cursor": 3, "id": 0}】 |
| Heavy payload filtering (numeric ranges, geo, JSON metadata) with minimal extra services | Qdrant | Payload filtering is evaluated during HNSW traversal, keeping latency stable even with complex predicates【{"cursor": 4, "id": 1}】 |
| Database‑managed vectorization (text2vec‑transformers) and hybrid BM25 + vector search out of the box | Weaviate | Built‑in text2vec‑transformers module runs on GPU, and the engine fuses BM25 and vector scores via a single alpha parameter【{"cursor": 4, "id": 0}】 |
Neutral note: All three engines accept pre‑computed embeddings, so a separate embedding service (e.g., a TEI container) can be used with any of them, decoupling the model from the storage choice.
milvusdb/milvus:v2.5.27-gpu)【{"cursor": 3, "id": 1}】.All statements are derived from the linked official documentation and the Spheron comparison article.