Leaving Chroma Cloud: to VecturaDB
Disclaimer. These guides and the scripts they link are provided as is, without warranty of any kind, as stated in the Apache License 2.0. They describe what we measured on 25 October 2026: your data, versions and setup may behave differently. Back up your data and test the migration on a copy first. You are responsible for your data and for every migration you run.
Not sure about your case? Open an issue in the VecturaDB repository, describe your data and your setup, and we will take a look on a best-effort basis.
In our tests no data was lost. The CLI of VecturaDB copied every record with its document, metadata, embedding and URI, and every collection with its metadata, its schema and its sparse and BM25 indexes. Then verify.py found every record the same as on Chroma Cloud, sparse vectors included. Two things do not arrive as they are:
- the settings of the dense index: Chroma Cloud uses SPANN, VecturaDB uses HNSW with its default settings;
- collections created with the old
hnsw:*settings in their metadata: the CLI stops on them, and they need our script; if their metadata hold sparse vectors, our script keeps those in a file, one line per record. See What arrives.
VecturaDB is a single-node server built from Chroma. It serves the Chroma API, and it has sparse vectors, BM25, the Search API, hybrid search and collection fork on a single node. It runs as one container, as a binary, or inside a Python application. Your Chroma clients keep working with it, in Python, JavaScript, .NET and the other languages we tested.
VecturaDB is our project, like ChromaDotNet. They are not affiliated with Chroma. We ran every command below on 25 October 2026, against Chroma Cloud and against VecturaDB 1.0.0-preview.3.
1. Start VecturaDB
With Docker
docker network create vecturadb-net
docker run -d --name vecturadb --network vecturadb-net -p 127.0.0.1:8000:8000 -v ./vecturadb-data:/data ghcr.io/vecturadb/vecturadb:1.0.0-preview.3
The image keeps its data in /data.
Without Docker
The chroma CLI of VecturaDB is one binary, for Windows x64, Linux x64 and arm64, macOS x64 and arm64. On Linux and macOS:
curl -sSL https://raw.githubusercontent.com/VecturaDB/VecturaDB/main/rust/cli/install/install.sh | bash
In our run on Linux, as a normal user, it went to ~/.local/bin. On Windows, in PowerShell:
iex ((New-Object System.Net.WebClient).DownloadString('https://raw.githubusercontent.com/VecturaDB/VecturaDB/main/rust/cli/install/install.ps1'))
It goes to %USERPROFILE%\bin. Then:
chroma run --path ./vecturadb-data --host 127.0.0.1 --port 8000
--host 127.0.0.1 matters: without it the server listens on localhost, and on our Windows machine 127.0.0.1 got no answer.
2. Copy the data
The chroma CLI of VecturaDB copies from Chroma Cloud. It needs your Chroma Cloud API key and the name of your database.
With Docker
The CLI is in the same image:
export CHROMA_API_KEY=<your Chroma Cloud API key>
docker run --rm --network vecturadb-net -e CHROMA_API_KEY --entrypoint bash ghcr.io/vecturadb/vecturadb:1.0.0-preview.3 -c \
'chroma login --api-key "$CHROMA_API_KEY" --profile cloud &&
chroma copy --from-cloud --to-local --host http://vecturadb:8000 --db <database> --all'
Without Docker
With the CLI you installed:
chroma login --api-key "$CHROMA_API_KEY" --profile cloud
chroma copy --from-cloud --to-local --host http://127.0.0.1:8000 --db <database> --all
What arrives
--all copies every collection of the database. To copy only some, use --collections <name> once per collection.
Every record arrives, with its document, metadata, embedding and URI; the metadata of the collection; and its schema, sparse and BM25 indexes included. Sparse and BM25 searches gave the same scores as on Chroma Cloud. The settings of the dense index do not arrive: Chroma Cloud uses SPANN, VecturaDB uses HNSW with its default settings.
The copy stops at the first collection it cannot create, and exits with code 1. The collections copied before it stay. Chroma Cloud collections created with the old hnsw:* settings in their metadata fail with Cannot set both collection config and schema simultaneously, as they do with Chroma. Copy those with our script copy_without_sparse.py, and the rest with --collections. If their metadata hold sparse vectors, the script keeps those in a file, one line per record, instead of the collection.
3. Switch the client
Only the line that creates the client changes. The collections are in the default tenant and database of the server. With each of the three clients, Python, JavaScript and .NET, the same query on the same copied collection gave the same records on Chroma Cloud and on VecturaDB.
The examples use 127.0.0.1. With Docker Desktop on Windows, localhost went to the IPv6 port of the container, which closed the connection; 127.0.0.1 worked everywhere.
Python
pip uninstall -y chromadb
pip install VecturaDB
The VecturaDB package keeps the import name chromadb, so existing code runs as it is.
import chromadb
# Before:
# client = chromadb.CloudClient(api_key="...", tenant="...", database="...")
client = chromadb.HttpClient(host="127.0.0.1", port=8000)
chromadb 1.5.9 works too, also for sparse vectors, BM25 and hybrid search against VecturaDB. For BM25, the Python client needs pip install snowballstemmer, with either package.
Without a server, the VecturaDB package opens the folder of step 1 inside your application, once the server is stopped. In our run it found both copied collections, the dense one and the BM25 one, and BM25 search worked:
client = chromadb.PersistentClient(path="./vecturadb-data")
JavaScript
chromadb 3.5.0 on npm, with VecturaDB as the server:
import { ChromaClient } from "chromadb";
// Before:
// const client = new CloudClient({ apiKey: "...", tenant: "...", database: "..." });
const client = new ChromaClient({ host: "127.0.0.1", port: 8000, ssl: false });
.NET
ChromaDotNet.Client 2.18.0:
using ChromaDB.Client;
// Before:
// var options = ChromaConfigurationOptions.FromConnectionString("Endpoint=https://api.trychroma.com;Token=...;Tenant=...;Database=...");
var options = ChromaConfigurationOptions.FromConnectionString("Endpoint=http://127.0.0.1:8000");
using var http = new HttpClient();
var client = new ChromaClient(options, http);
Aspire
ChromaDotNet.Aspire 0.2.8 runs VecturaDB for the AppHost when you give it the image. In our run the container started on 127.0.0.1 and its pre-flight checks answered VecturaDB 1.0.0-preview.3:
// AppHost
var chroma = builder.AddChroma("chroma")
.WithImageRegistry("ghcr.io").WithImage("vecturadb/vecturadb", "1.0.0-preview.3");
builder.AddProject<Projects.ApiService>("api").WithReference(chroma).WaitFor(chroma);
// Service
builder.AddChromaClient("chroma");
Testcontainers
ChromaDotNet.Testcontainers 0.1.7 starts VecturaDB for your tests when you give it the image. In our run the Search API answered there, and not on chromadb/chroma:1.5.9:
await using var chroma = new ChromaBuilder("ghcr.io/vecturadb/vecturadb:1.0.0-preview.3").Build();
await chroma.StartAsync();
Other languages
The same short test with each client (create, add, query, get, where, delete, count) gave the same result on VecturaDB 1.0.0-preview.3 and on Chroma 1.5.9:
| Language | Client | Result |
|---|---|---|
| Go | github.com/amikos-tech/chroma-go v0.4.1 |
Every step passed. go get needed replace github.com/amikos-tech/chroma-go-local => github.com/amikos-tech/chroma-go-local v0.3.5 in go.mod: v0.3.4, which it asks for, was not on proxy.golang.org |
| PHP | helgesverre/chromadb 3.0.0 |
Every step passed |
| PHP | codewithkyrian/chromadb-php 1.0.0 |
Every step passed |
| Dart | chromadb 2.0.0 |
Every step passed |
| Elixir | chroma 0.1.3 |
The steps the client has passed |
| Ruby | chroma-db 0.9.0 |
get and delete failed on both servers |
Every client we tested, with its result on each server: Clients.
The API keys
Without API keys VecturaDB accepts every request, like open-source Chroma. With a file of users and keys it checks the x-chroma-token header, as Chroma Cloud does: see API keys. The server does not serve HTTPS: put a TLS proxy in front of it to expose it beyond your machine.
4. Hosted embedding models
chroma-cloud-qwen and chroma-cloud-splade run on Chroma Cloud and stop with it. See Hosted embedding models: the same open models, run locally, gave the same search results in our tests. VecturaDB keeps the SPLADE vectors of your collections, so sparse search keeps working.
5. Check
verify.py compares each collection on both sides: count, ids, documents, metadata with the sparse vectors, URIs, embeddings, and the distances of three dense queries.
curl -O https://raw.githubusercontent.com/VecturaDB/VecturaDB/eeec42dbacd6ab2d4efec2b6a899dac25f0846d0/migration/leaving-chroma-cloud/verify.py
pip install chromadb==1.5.9
export CHROMA_API_KEY=<key> CHROMA_TENANT=<tenant> CHROMA_DATABASE=<database>
python verify.py 127.0.0.1 8000 <name> <name>
In our run, all records were the same, sparse vectors included. One difference is worth knowing. HNSW is approximate: in a test collection with 300 identical embeddings, it did not find the two records at distance 0, which Chroma Cloud found. Chroma 1.5.9 does the same. With real data, run verify.py on your largest collections.
Opening the same data with Chroma 1.5.9
In our tests, Chroma 1.5.9 opened the data folder of VecturaDB with no error and no loss. It read every record, document and metadata value. It did not read the sparse vectors, which stayed in the database. Sparse, BM25 and Search API queries failed there with "not implemented".
The other way also worked: a collection written by Chroma 1.5.9 opened, and worked, in VecturaDB.
Writing with Chroma 1.5.9 into a VecturaDB collection with a BM25 index is different:
- With the Python client
chromadb1.5.9, adding a record or changing a document failed: Chroma 1.5.9 answeredSparse vector is not supported for local chroma. Changes to metadata only went through. - A client that writes without the BM25 function, for example over HTTP, got through. Back in VecturaDB, a record added that way had no BM25 vector, and BM25 search did not find it. A document changed that way kept the BM25 vector of its old text.
To fix those records, write their documents again with VecturaDB and the Python client, with their embeddings:
collection.update(ids=ids, documents=documents, embeddings=embeddings)
In our tests, BM25 search then found them, with their new text.
So we suggest not writing with Chroma 1.5.9 to VecturaDB data that uses sparse vectors or BM25.