Leaving Chroma Cloud

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:

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:

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.