The short version: a local Chroma PersistentClient writes your indexes to one machine's disk — fine for a prototype, a single point of failure in production. Chroma Cloud (GA since Q1 2026) keeps them on object storage behind stateless query nodes. Moving is one command: chroma login, then chroma copy --from-local --to-cloud --all --db <name>. It lifts every collection — vectors and metadata — with no re-embedding. This walkthrough covers that path, the per-collection variant for a staged cutover, and the two fallbacks for when the CLI can't reach both ends.
The one-command path#
If the machine holding your local data can also reach the internet, you're done in two commands. First authenticate — this opens a browser and links the CLI to your Chroma Cloud account:
chroma login
Then copy everything into a cloud database:
chroma copy --from-local --to-cloud --all --db production
--all takes every collection in your local store. --db production is the target Chroma Cloud database (create it in the dashboard first, or point at an existing one). The copy carries the stored embeddings and metadata verbatim — you are not re-embedding, so there's no inference bill and no chance a different embedding model quietly shifts your retrieval results.
Move one collection at a time#
For a staged cutover — validate a single collection in the cloud before you commit the whole corpus — name the collection instead of passing --all:
chroma copy --from-local --to-cloud my_docs --db production
Run your query suite against the cloud copy, confirm recall matches, then repeat for the next collection. This is the safe way to migrate a large store: you're never in a half-broken state where the app doesn't know which backend holds the truth.
The copy is additive and idempotent on collection identity — you can re-run it, and you can keep writing to local until you flip the client. Nothing forces a big-bang switch.
When the CLI can't see both ends#
Air-gapped production, a CI job, or a prod box that can't run an interactive chroma login all break the one-command path. Two fallbacks:
ChromaDB Data Pipes decouples the two halves. Export the source to a portable stream on the machine that can read it, ship the file, then import into the cloud target:
cdp export "file:///data/chroma/my_docs" > my_docs.jsonl
cdp import "https://api.trychroma.com/..." < my_docs.jsonl
A batched client-to-client loop is the dependency-free version — read from the local client in pages, write to the CloudClient:
import chromadb
src = chromadb.PersistentClient(path="/data/chroma")
dst = chromadb.CloudClient(api_key="ck-...", tenant="...", database="production")
s = src.get_collection("my_docs")
d = dst.get_or_create_collection("my_docs")
N = 1000
total = s.count()
for off in range(0, total, N):
batch = s.get(limit=N, offset=off, include=["embeddings", "documents", "metadatas"])
d.add(ids=batch["ids"], embeddings=batch["embeddings"],
documents=batch["documents"], metadatas=batch["metadatas"])
Page size 1,000 keeps memory flat on a large collection. include=["embeddings", ...] is the important part — omit it and you'll re-embed on the far side, which is slower and can change results.
What actually changes underneath#
The reason to bother is architectural, not cosmetic. A local PersistentClient binds your index to one disk. Chroma Cloud's serverless design splits the work: query nodes are stateless and serve indexes read from object storage plus a local cache; compactor nodes build indexes asynchronously and persist them back to object storage; a distributed WAL gives durability ahead of compaction. Because query nodes hold no permanent state, reads scale by adding nodes — there's no re-sharding step and no single box to outgrow. It's the same object-storage-first bet that LanceDB and Turbopuffer are making from the other direction.
Your application code barely notices. Swap the constructor — PersistentClient(path=...) becomes CloudClient(api_key=..., tenant=..., database=...) — and every collection.query(...), filter, and where clause stays identical. That's the whole migration: one command to move the data, one line to point the app at it.
If you're still deciding whether Chroma is the right destination at all, the open-source RAG platform roundup puts it next to the alternatives before you commit.



