Как установить и настроить Chroma на VPS
Chroma ставится одной строкой pip install chromadb — и ровно поэтому её чаще всего запускают неправильно. Сначала она живёт библиотекой внутри приложения, потом её переносят на сервер, и там она слушает 0.0.0.0:8000 без пароля, отвечает старым клиентам кодом 410 и падает на первом же батче в десять тысяч записей. Разберём установку Chroma на VPS целиком: режимы работы, Docker Compose и systemd, токен с TLS, размерность коллекции, реальные лимиты и бэкап.
Содержание
- Chroma в двух режимах: библиотека и сервер
- Установка на VPS: Docker Compose или venv под systemd
- Закрываем доступ: токен, htpasswd и Nginx с TLS
- Первая коллекция: размерность, метрика и кто считает эмбеддинги
- Грабли, о которых узнают на проде
- Память, диск, вакуум и бэкап
- Какой сервер под Chroma брать в MAATRIX
Обсудить статью, задать вопрос или начать новую тему
Есть вопрос по этой статье, идея для обсуждения или просто хотите поделиться опытом? Сообщество MAATRIX ждёт. Для общения, пожалуйста, зарегистрируйтесь в нашем личном кабинете.
Перейти в сообщество →Chroma в двух режимах: библиотека и сервер
Chroma — открытая векторная база на Python-обвязке с ядром на Rust (переписано в релизе 1.0). Она хранит документы, эмбеддинги и метаданные рядом, ищет по косинусу или евклиду с фильтрами вида «только там, где lang = ru». Тексты и метаданные лежат в SQLite, векторный индекс — в бинарных файлах HNSW.
Работать с ней можно тремя способами:
chromadb.EphemeralClient()— всё в оперативной памяти, после перезапуска пусто. Только для тестов.chromadb.PersistentClient(path="/var/lib/chroma")— встроенный режим: файлы на диске, но внутри вашего процесса, отдельного сервиса нет.chromadb.HttpClient(host=..., port=8000)— клиент-серверный режим: отдельный процессchroma runи HTTP API.
Встроенный режим подкупает простотой, но каталог базы открывается на запись только одним процессом: два воркера Gunicorn плюс cron-скрипт индексации дадут sqlite3.OperationalError: database is locked или разъехавшийся индекс.
Порт по умолчанию один — 8000, HTTP.
Второе, что ломает интеграции, — смена версии API: с релиза 1.0 (апрель 2025) пути /api/v1/... убраны. Старый клиент получит от нового сервера HTTP 410 и такое тело:
{"error":"Unimplemented","message":"The v1 API is deprecated. Please use /v2 apis"}
Живой сервер проверяется без всякого SDK:
curl -s http://127.0.0.1:8000/api/v2/heartbeat
curl -s http://127.0.0.1:8000/api/v2/version
{"nanosecond heartbeat":1756370411123456789}
"1.5.9"
В v2 появились арендаторы и базы: по умолчанию default_tenant и default_database, и они же лягут в URL.
И третья граница: эмбеддинги Chroma не считает — это отдельный разговор, ниже.
Установка на VPS: Docker Compose или venv под systemd
Ставим на чистую Ubuntu 24.04, подходит и Debian 12. Контейнер экономит нервы: образ уже собран с нужной версией SQLite и chroma-hnswlib.
Вариант с Docker Compose. Каталог /opt/chroma, файл compose.yml:
services:
chroma:
image: chromadb/chroma:latest
container_name: chroma
restart: unless-stopped
ports:
- "127.0.0.1:8000:8000"
volumes:
- /var/lib/chroma:/data
environment:
- ANONYMIZED_TELEMETRY=FALSE
- IS_PERSISTENT=TRUE
Три вещи здесь важнее прочих.
127.0.0.1:8000:8000, а не8000:8000. Docker правитiptablesв обход UFW: на всех интерфейсах база окажется в интернете, даже еслиufw statusпишетdeny 8000.- Точка монтирования
/data. В образах 1.x каталог данных именно такой, в 0.4–0.5 он назывался/chroma/chroma. Старый том по привычному пути после обновления не подхватится — сервер поднимется с пустой базой молча, без единой ошибки в логе. - Тег
latestв проде — плохая идея. Узнайте фактический номер через/api/v2/versionи пропишите его явно, напримерchromadb/chroma:1.5.9. Клиент и сервер держите одной серии, иначе вернётся та самая ошибкаUnimplemented.
cd /opt/chroma && docker compose up -d
docker compose logs -f chroma | head -20
Вариант без Docker — venv под systemd. Пригодится, если контейнеров на машине больше не планируется.
apt update && apt install -y python3-venv python3-dev build-essential
useradd -r -s /usr/sbin/nologin -d /var/lib/chroma chroma
mkdir -p /var/lib/chroma /opt/chroma && chown -R chroma:chroma /var/lib/chroma
python3 -m venv /opt/chroma/venv
/opt/chroma/venv/bin/pip install -U pip "chromadb==1.5.*"
Здесь ждут три типичных отказа. Первый — попытка поставить пакет системным pip на Ubuntu 24.04:
error: externally-managed-environment
× This environment is externally managed
Это PEP 668 — обходить --break-system-packages не надо, venv решает вопрос сам. Второй отказ — нет компилятора: error: command 'gcc' failed: No such file or directory на chroma-hnswlib, лечится build-essential с python3-dev. На старых дистрибутивах (Debian 11 и его SQLite 3.34) добавится третий: RuntimeError: Your system has an unsupported version of sqlite3. Chroma requires sqlite3 >= 3.35.0. — лечится pip install pysqlite3-binary с подменой модуля или переходом на контейнер.
Юнит /etc/systemd/system/chroma.service:
[Unit]
Description=Chroma vector database
After=network-online.target
[Service]
User=chroma
Group=chroma
WorkingDirectory=/var/lib/chroma
Environment=ANONYMIZED_TELEMETRY=FALSE
ExecStart=/opt/chroma/venv/bin/chroma run --path /var/lib/chroma --host 127.0.0.1 --port 8000
Restart=on-failure
RestartSec=5
[Install]
WantedBy=multi-user.target
Дальше systemctl daemon-reload && systemctl enable --now chroma. На стороне приложения тяжёлый пакет не нужен: pip install chromadb-client ставит тонкий HTTP-клиент без onnxruntime и tokenizers.
Нужен сервер под эту задачу?
Разверните VPS MAATRIX за пару минут: NVMe, AMD EPYC, root-доступ, локации UK, США, Франция и РФ. Оплата картой РФ и по СБП.
Арендовать серверЗакрываем доступ: токен, htpasswd и Nginx с TLS
По умолчанию аутентификации в Chroma нет вообще: любой, кто дотянулся до порта 8000, читает все коллекции и вызывает delete_collection(). Пока сервер привязан к 127.0.0.1, это терпимо; как только к нему пойдёт другой хост, нужен токен.
environment:
- CHROMA_SERVER_AUTHN_PROVIDER=chromadb.auth.token_authn.TokenAuthenticationServerProvider
- CHROMA_SERVER_AUTHN_CREDENTIALS=chr_7f2b9d4e1a6c8035bfa1
- CHROMA_AUTH_TOKEN_TRANSPORT_HEADER=X_CHROMA_TOKEN
Ловушка, на которой теряют вечера: в 0.5.0 переменные переименовали. Было CHROMA_SERVER_AUTH_PROVIDER и chromadb.auth.token.TokenAuthServerProvider, стало CHROMA_SERVER_AUTHN_* с буквой N и классом из token_authn. Старые имена новый сервер молча игнорирует: стартует, отвечает и пускает всех.
Второй нюанс: часть путей проверку обходит, и heartbeat без токена отвечает даже на защищённом сервере — проверка нужна на коллекциях:
curl -s -o /dev/null -w '%{http_code}\n' \
http://127.0.0.1:8000/api/v2/tenants/default_tenant/databases/default_database/collections
# 401
curl -s -H 'X-Chroma-Token: chr_7f2b9d4e1a6c8035bfa1' \
http://127.0.0.1:8000/api/v2/tenants/default_tenant/databases/default_database/collections | jq length
Есть и вариант с логином и паролем — chromadb.auth.basic_authn.BasicAuthenticationServerProvider с файлом server.htpasswd. Клиент на Python:
import chromadb
from chromadb.config import Settings
client = chromadb.HttpClient(
host="chroma.example.com", port=443, ssl=True,
settings=Settings(
chroma_client_auth_provider="chromadb.auth.token_authn.TokenAuthClientProvider",
chroma_client_auth_credentials="chr_7f2b9d4e1a6c8035bfa1",
chroma_auth_token_transport_header="X_CHROMA_TOKEN",
),
)
TLS Chroma не умеет — наружу её выставляют через Nginx как реверс-прокси с сертификатом Let's Encrypt:
server {
listen 443 ssl http2;
server_name chroma.example.com;
ssl_certificate /etc/letsencrypt/live/chroma.example.com/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/chroma.example.com/privkey.pem;
client_max_body_size 128m;
location / {
proxy_pass http://127.0.0.1:8000;
proxy_set_header Host $host;
proxy_read_timeout 300s;
proxy_buffering off;
}
}
client_max_body_size здесь не украшение: батч из нескольких тысяч векторов по 1536 измерений — это десятки мегабайт JSON, а дефолтный лимит 1 МБ отдаст 413 Request Entity Too Large раньше, чем Chroma увидит запрос. И не вешайте сюда же auth_basic: заголовок Authorization достанется Nginx, а X_CHROMA_TOKEN до клиента не доедет. Фаервол закрывает вопрос:
ufw allow 22/tcp && ufw allow 443/tcp && ufw deny 8000/tcp && ufw enable
Первая коллекция: размерность, метрика и кто считает эмбеддинги
Коллекция в Chroma — индекс плюс схема, которую задают один раз. Навсегда фиксируются два параметра: размерность вектора и метрика расстояния.
col = client.get_or_create_collection(
name="docs",
configuration={"hnsw": {"space": "cosine", "ef_construction": 200,
"max_neighbors": 32, "ef_search": 100}},
)
col.add(ids=["d1", "d2"],
documents=["первый абзац", "второй абзац"],
metadatas=[{"lang": "ru"}, {"lang": "ru"}])
print(col.query(query_texts=["о чём первый"], n_results=2, where={"lang": "ru"}))
Метрика по умолчанию — l2, а не косинус. Для нормализованных эмбеддингов порядок результатов совпадёт, для ненормализованных — разъедется и сам результат. space у созданной коллекции не меняется — только пересоздать и залить заново. В 0.5.x параметры HNSW задавались через metadata={"hnsw:space": "cosine"}, в 1.x — через configuration; старые ключи ещё работают, но устарели.
Размерность берётся из первого вектора; попытка подмешать эмбеддинги другой модели даёт:
chromadb.errors.InvalidDimensionException: Embedding dimension 1536 does not match collection dimensionality 384
Это не баг, а смена модели на середине проекта.
Теперь главное. Эмбеддинги считает клиент, а не сервер. При вызове add(documents=[...]) embedding-функция считает в вашем процессе — на сервер уходят готовые числа. По умолчанию берётся all-MiniLM-L6-v2 в формате ONNX, 384 измерения; при первом вызове библиотека скачивает архив модели примерно на 79 МБ в ~/.cache/chroma/onnx_models/all-MiniLM-L6-v2/. На машине без исходящего доступа к AWS S3 первый add() повиснет с таймаутом загрузки, и слова «модель» в ошибке не будет.
Если эмбеддинги считает внешний API (text-embedding-3-small — 1536 измерений, text-embedding-3-large — 3072), нагрузки на Chroma нет, зато важен исходящий IP: с российского адреса OpenAI отвечает 403 unsupported_country_region_territory.
Если модель локальная — скажем, nomic-embed-text через Ollama на той же машине, — CPU становится общим дефицитным ресурсом. Наш замер на AMD EPYC 9554 (16 vCPU, это 8 ядер плюс HT, Ollama 0.33.1, qwen2.5:7b в Q4_K_M): при num_thread 2 / 4 / 8 / 16 генерация даёт 5,7 / 7,6 / 7,6 / 7,4 ток/с — полка уже на четырёх потоках, упор в память, а 32 потока роняют скорость до 0,35 ток/с, в двадцать раз. Потоками инференс не разгонишь, а Chroma на тех же ядрах задушить легко.
Грабли, о которых узнают на проде
Лимит батча. Chroma не принимает произвольно большой add(). Сервер честно сообщает свой предел:
curl -s http://127.0.0.1:8000/api/v2/pre-flight-checks
{"max_batch_size":5461}
Число не случайное — лимит переменных SQLite в одном запросе, делённый на число колонок. Превышение даёт ValueError: Batch size 20000 exceeds maximum batch size 5461. Лечение — резать заливку самому:
B = client.get_max_batch_size()
for i in range(0, len(ids), B):
col.add(ids=ids[i:i+B], embeddings=vecs[i:i+B], metadatas=meta[i:i+B])
Запрос больше, чем коллекция. Просьба вернуть n_results=10 из коллекции с тремя элементами в ряде версий выдаёт не пустоту, а исключение из hnswlib — с фирменной опечаткой в тексте: RuntimeError: Cannot return the results in a contigious 2D array. Probably ef or M is too small. Причина не в ef, а в том, что n_results больше числа доступных точек, — ограничивайте его через col.count().
Один узел — и точка. Реплик, шардов и автофейловера в открытой сборке нет: две Chroma над одним каталогом не поднять, а сетевые ФС вроде NFS дают database is locked уже на средней нагрузке — каталог должен лежать на локальном диске. Кластер и раздельные снапшоты — это к Qdrant, и его лучше выбрать заранее, чем мигрировать миллион векторов потом.
Память, диск, вакуум и бэкап
Индекс HNSW держится целиком в оперативной памяти. Это арифметика, а не измерение: на каждый вектор уходит размерность × 4 байта (float32) плюс граф связей — при max_neighbors = 16 ещё около 140 байт на запись.
| Размерность | Байт на вектор | 100 тыс. векторов | 1 млн векторов |
|---|---|---|---|
| 384 (MiniLM) | ~1,7 КБ | ~170 МБ | ~1,7 ГБ |
| 768 (nomic-embed) | ~3,2 КБ | ~320 МБ | ~3,2 ГБ |
| 1536 (OpenAI small) | ~6,3 КБ | ~630 МБ | ~6,3 ГБ |
Это нижняя граница самого индекса: сверху лягут процесс Python, кеш SQLite и страничный кеш ФС — закладывайте плюс 30–50 %. Впритык к RAM базу не планируйте: индекс перестаёт помещаться — машина уходит в своп, поиск замедляется на порядки. При max_neighbors = 32 граф стоит вдвое дороже, около 270 байт на вектор.
На диске каталог выглядит так:
/var/lib/chroma/
├── chroma.sqlite3
└── 4f1c0a3e-....-9b2d/
├── data_level0.bin
├── header.bin
├── length.bin
└── link_lists.bin
Отдельного внимания стоит chroma.sqlite3: каждая запись сначала попадает в журнал внутри него и лишь потом переносится в HNSW-индекс, а журнал не подчищается сам. Файл разрастается кратно полезным данным, и удаление коллекций места не возвращает. Лечение — остановить сервер и прогнать вакуум:
systemctl stop chroma # или docker compose stop chroma
/opt/chroma/venv/bin/chroma utils vacuum --path /var/lib/chroma
systemctl start chroma
Операция долгая — на больших базах минуты и десятки минут, — зато потом журнал чистится сам.
Штатного API снапшотов у Chroma, в отличие от Qdrant, нет: chroma.sqlite3 и бинарники HNSW пишутся независимо, и горячая копия «на живую» почти наверняка развалится. Рабочий бэкап — только с остановкой:
docker compose stop chroma
tar -czf /backup/chroma-$(date +%F).tar.gz -C /var/lib chroma
docker compose start chroma
Пара секунд простоя раз в сутки по cron — приемлемая цена. Восстановление симметрично: остановить сервис, распаковать каталог, поднять — версия должна быть не ниже той, на которой снимали бэкап.
Какой сервер под Chroma брать в MAATRIX
Chroma — сервис памяти и диска, а не процессора: HNSW нагружает CPU короткими всплесками, а индекс обязан целиком помещаться в RAM.
Минимум: 1 vCPU, 2 ГБ RAM, 20 ГБ NVMe. Хватает на прототип и базу до ~300 тысяч векторов по 384 измерения, если эмбеддинги считает внешний API. Ограничение честное: на 1 ГБ не ставьте — процесс с зависимостями съедает сотни мегабайт ещё до первой коллекции, и первый крупный add() закончится OOM.
Комфортный вариант: 2–4 vCPU, 8 ГБ RAM, 80 ГБ NVMe. Это миллион векторов по 768 измерений (около 3,2 ГБ индекса) с запасом на журнал SQLite, вакуум и tar-архив рядом. Диск берите с трёхкратным запасом от размера индекса — там же живут журнал и бэкап.
Локальные эмбеддинги меняют картину. Модель и база делят одни ядра, а потоками, как показал замер выше, инференс не разгоняется. Для связки «Chroma + локальная embedding-модель + LLM» разумный старт — 8 vCPU и 16 ГБ RAM: это уже территория выделенного сервера.
Локация — Великобритания, Лондон. Причина прикладная: RAG-стек держат рядом с приложением, которое его дёргает, — лишние 40–50 мс на каждом обращении к базе за один ответ заметно накапливаются. С британского адреса штатно отвечают внешние embedding-API, тогда как 403 unsupported_country_region_territory с российского IP ломает связку целиком. Если в документах персональные данные россиян и вы работаете по 152-ФЗ — берите RU-локацию, но сразу планируйте локальную модель эмбеддингов.
Про установку без иллюзий. Chroma в каталоге приложений apps.maatrix.io нет: сервер приезжает чистым, с Ubuntu 24.04, и разворачивается по инструкции выше — минут за десять вместе с Nginx и сертификатом. Зато в каталоге есть готовые сборки соседей по AI-стеку — например, LiteLLM для маршрутизации между моделями: их отмечают при заказе, и они поднимаются сами. Удобно, когда Chroma нужна как часть RAG по своим документам.
Оплата — картами российских банков, по СБП, криптовалютой или токеном MAAT: иностранная карта не нужна, хотя сервер стоит в Лондоне. После выдачи доступов первым делом закройте порт 8000 фаерволом и включите токен — открытую базу с документами компании сканеры находят за минуты.
Нужен сервер под эту задачу?
Разверните VPS MAATRIX за пару минут: NVMe, AMD EPYC, root-доступ, локации UK, США, Франция и РФ. Оплата картой РФ и по СБП.
Арендовать серверОбсудить статью, задать вопрос или начать новую тему
Есть вопрос по этой статье, идея для обсуждения или просто хотите поделиться опытом? Сообщество MAATRIX ждёт. Для общения, пожалуйста, зарегистрируйтесь в нашем личном кабинете.
Перейти в сообщество →Частые вопросы
Chroma или Qdrant — что выбрать под RAG?
Chroma выигрывает на старте: контейнер, знакомый Python-API, документы и метаданные вместе. Qdrant — на дистанции: снапшоты по API, квантование, кластер. Водораздел практический — примерно миллион векторов и бэкап без остановки.
Можно ли обойтись PersistentClient и не поднимать сервер?
Да, если писатель — ровно один процесс: скрипт индексации, одиночный воркер. Появится второй воркер, cron рядом или вторая машина — упрётесь в database is locked.
Удалил коллекцию, а место на диске не освободилось. Почему?
Записи попадают в журнал chroma.sqlite3, который сам не подчищается: удаление лишь помечает данные, файл не сжимается. Остановите сервис и выполните chroma utils vacuum --path /var/lib/chroma.
Нужны сами нейросети для контента?
Генерируйте изображения, видео и озвучку нейросетями на falapi.io — десятки моделей в одном окне. Оплата картой РФ и по СБП.