Here’s the bit almost nobody puts on the install page: Chroma’s HNSW index has to live entirely in RAM. For typical 1024-dimensional embeddings, the single-node performance guide puts max collection size at roughly N ≈ R × 0.245 million vectors (N in millions, R = RAM in GB). Under 2GB system RAM is not recommended. That math matters more than the pip one-liner when you treat Chroma as an embedded vector database.
This guide deploys Chroma 1.5.9 (current stable chromadb on PyPI as of May 2026) for local embedded use – PersistentClient on disk, optional client-server, Docker if you want a process boundary. Commands first. Theory later, if at all.
System requirements before you install
Python ≥ 3.9. That’s the floor on the PyPI classifiers for 1.5.9. OS-independent package, with native wheels for manylinux x86_64/aarch64, macOS x86_64/arm64, and Windows amd64.
| Resource | Minimum | Comfortable for real work |
|---|---|---|
| RAM | 2 GB (official floor) | 8-16 GB+ → ~1.7M-3.6M of 1024-d vectors via the formula |
| CPU | 1 core | 2+ vCPUs if you embed and query at once |
| Disk | a few GB free | at least RAM size + headroom; SSD preferred |
| Python | 3.9+ | 3.10+ (often newer SQLite) |
| Docker (optional) | working Engine + pull access | volume mount you actually control |
Latency does not ease off when the index spills. Swap starts and the box feels dead. Size RAM from the formula, then leave about 1GB for everything else on the machine.
Official download sources for Chroma 1.5.9
Skip random mirrors. Use these only:
- Python package:pypi.org/project/chromadb – pin
chromadb==1.5.9 - Docs:docs.trychroma.com getting started
- Source & releases:github.com/chroma-core/chroma/releases (tag
1.5.9) - Docker:
chromadb/chroma:1.5.9on Docker Hub (also GHCR) - Product home:trychroma.com
Pin :1.5.9. :latest is fine until it isn’t – main drifts; install day wants the tagged stable.
Install Chroma 1.5.9 step by step
Embedded path that works: virtualenv + pip. Library, default embedding stack, and the chroma CLI land together.
# 1. Fresh venv (Linux/macOS)
python3 -m venv .venv
source .venv/bin/activate
# Windows PowerShell
# python -m venv .venv
# ..venvScriptsActivate.ps1
# 2. Upgrade installer tooling
python -m pip install --upgrade pip
# 3. Install exact stable release
pip install "chromadb==1.5.9"
# 4. Confirm package version
python -c "import chromadb; print(chromadb.__version__)"
Expect 1.5.9. No chroma on PATH inside the venv? Call the script from the venv’s bin, or pipx install chromadb for a global CLI without touching system Python (CLI install docs).
Brief alternatives: JS/TS → npm install chromadb then npx chroma run --path ./data. Process isolation → Docker below. HTTP-only Python client → chromadb-client when the server already runs elsewhere.
First-time configuration (minimum viable embedded setup)
chromadb.Client() is a 30-second smoke test. Process exits, data is gone. For a real on-disk store, use PersistentClient and a path you own:
import chromadb
from pathlib import Path
data_dir = Path("./chroma-data").resolve()
data_dir.mkdir(parents=True, exist_ok=True)
client = chromadb.PersistentClient(path=str(data_dir))
collection = client.get_or_create_collection(name="local_embed_store")
collection.upsert(
ids=["cfg-1"],
documents=["Embedded Chroma 1.5.9 configuration check"],
metadatas=[{"env": "local"}],
)
print(client.heartbeat())
print(collection.count())
Omit path and you get .chroma in the working directory – easy to lose across projects. Set it on purpose.
Same machine, separate process:
chroma run --path ./chroma-data --host localhost --port 8000
Connect with chromadb.HttpClient(host="localhost", port=8000). Docker form from the Docker deploy guide:
docker run --rm -v "$(pwd)/chroma-data:/data" -p 8000:8000 chromadb/chroma:1.5.9
Optional /config.yaml mount exists in that guide if you need server-side knobs. Keep reset disabled on any volume you care about so one sloppy client call cannot wipe it.
Think of PersistentClient like SQLite for vectors: same process, files on disk, zero network until you outgrow the laptop. That model keeps config boring – in a good way – and stops the “three servers, three folders, where did my collection go?” loop.
Verify the install actually works
Three checks, in order: import/version, heartbeat, round-trip write.
python - <<'PY'
import chromadb
print("version", chromadb.__version__)
c = chromadb.PersistentClient(path="./chroma-data")
print("heartbeat_ns", c.heartbeat())
col = c.get_or_create_collection("verify")
col.upsert(ids=["v1"], documents=["ping"])
assert col.count() >= 1
print("ok", col.get(ids=["v1"]))
PY
Server or Docker: hit the heartbeat endpoint the running process exposes (docs show the HTTP client path). Connection refused means nothing is listening. Heartbeat OK but the app still fails? Wrong host/port, or the client’s data path is not the server’s --path//data.
Common install errors and fixes
Match the message. Smallest fix wins. These are the ones that show up in issues and deploy logs (troubleshooting docs):
- “Your system has an unsupported version of sqlite3” / Chroma requires SQLite > 3.35 – Newer Python (3.10+ often helps). Linux:
pip install pysqlite3-binary, then overridesqlite3before importing chromadb. Windows: replace the SQLite DLL next to that Python. Debian images: bookworm or newer. - Failed to build wheels for hnswlib –
export HNSWLIB_NO_NATIVE=1, reinstall. Mac:xcode-select --install. Windows: follow the build toolchain notes linked from troubleshooting. - Illegal instruction (core dumped) in Docker – image/CPU arch mismatch. Pull or build the tag that matches the machine that runs it.
Pip stuck on download? Pin 1.5.9 and retry on a cleaner network path. Binary wheels exist for the common platforms; you should not be compiling the world unless the wheel miss-matches your arch.
Upgrade, vacuum, and uninstall
pip install -U "chromadb==1.5.9"
python -c "import chromadb; print(chromadb.__version__)"
The catch: data directories that lived on Chroma before v0.5.6 can keep an unpruned write-ahead log. After you upgrade, run vacuum once so the dir shrinks and continuous pruning turns on (migration notes):
chroma utils vacuum --path ./chroma-data
Brand-new 1.5.x dirs already prune. Don’t cron vacuum “just in case.”
Uninstall / cleanup:
# Package
pip uninstall chromadb -y
# Optional thin client
pip uninstall chromadb-client -y
# Data (destructive)
rm -rf ./chroma-data ./.chroma
# Docker
docker rm -f $(docker ps -aq --filter ancestor=chromadb/chroma:1.5.9) 2>/dev/null
docker volume prune -f # only if you used named volumes you no longer need
pipx CLI: pipx uninstall chromadb. Curl-installed binaries: remove whatever path the standalone installer used.
Honest question worth sitting with: how many “local RAG stacks” are three containers and a reverse proxy when a venv and PersistentClient would have held the whole prototype? Complexity is optional until a second language or a second user shows up.
FAQ
Is Chroma 1.5.9 free for embedded local use?
Yes. Apache 2.0 on the open-source server and Python package. Chroma Cloud is separate billing – you do not need it for PersistentClient or self-hosted Docker.
Should I use PersistentClient or Docker for a laptop RAG prototype?
Python, single user, one project folder? PersistentClient. Fewer moving parts. Move to chroma run or chromadb/chroma:1.5.9 when another language needs HTTP, you want process isolation, or you are staging a small VPS. Point both at the same host path//data volume if you switch – never two writers on one directory.
Why does heartbeat succeed but my old collection “disappear”?
Path mismatch, almost every time. Ephemeral Client() never touches disk. PersistentClient(path="./chroma-data") from project A is not Docker’s /data unless that exact host path is mounted. Print Path(path).resolve() in the app and compare it to the server’s --path. Second gotcha: a startup reset() empties the store while heartbeat still looks healthy on the empty DB. Confirm you are not wiping on boot before you chase networking ghosts.
Next: create the venv, pin chromadb==1.5.9, run the verify snippet on ./chroma-data, point the embed pipeline at that same path. Heartbeat prints and count() survives a process restart? Install is done.