Deploy ChromaDB on a VPS
An open-source vector database for AI applications — semantic search, RAG pipelines, and recommendation systems on your own infrastructure.
Introduction
ChromaDB is an open-source vector database designed for AI applications. It stores and retrieves high-dimensional vector embeddings alongside metadata, making it a solid backend for retrieval-augmented generation (RAG) pipelines, semantic search, and recommendation systems. Unlike heavyweight alternatives, ChromaDB is lightweight enough to run comfortably on a budget VPS while handling production workloads for small to medium-scale applications.
This guide walks through deploying ChromaDB on a RamNode VPS using Docker, configuring persistent storage, enabling token authentication, securing the port with a reverse proxy, and setting up automatic restarts.
Server Requirements
| Component | Recommended |
|---|---|
| RAM | 2 GB minimum (1 GB workable for small collections) |
| CPU | 1 vCPU minimum; 2+ vCPUs for concurrent embedding requests |
| Disk | 10 GB+ SSD; scale based on collection sizes |
| OS | Ubuntu 22.04 LTS or Debian 12 |
| RamNode Plan | KVM SSD 2 ($6/mo) or higher |
Prerequisites:
- • Docker and Docker Compose installed and running
- • A non-root user with sudo privileges
- • Ports 8000 (ChromaDB) and 443 (HTTPS) open in your firewall
- • A domain name pointed at your VPS IP (for the HTTPS/Nginx section)
Prepare the Server
Update packages and install Docker:
sudo apt update && sudo apt upgrade -y
# Install Docker
curl -fsSL https://get.docker.com | sh
sudo usermod -aG docker $USER
newgrp docker
# Verify Docker is running
docker --version
docker compose versionCreate the Project Directory
mkdir -p ~/chromadb/data
cd ~/chromadbThe data directory stores ChromaDB's persistent SQLite and vector index files. Keeping this separate from the config makes backups and upgrades straightforward.
Generate an Authentication Token
ChromaDB supports static token authentication. Generate a secure random token:
openssl rand -hex 32Save this token — you'll pass it as CHROMA_SERVER_AUTHN_CREDENTIALS and use it as a Bearer token in all client requests.
Write the Docker Compose File
nano ~/chromadb/docker-compose.ymlversion: '3.9'
services:
chromadb:
image: chromadb/chroma:latest
container_name: chromadb
restart: unless-stopped
ports:
- "127.0.0.1:8000:8000"
volumes:
- ./data:/chroma/chroma
environment:
- CHROMA_SERVER_AUTHN_PROVIDER=chromadb.auth.token_authn.TokenAuthenticationServerProvider
- CHROMA_SERVER_AUTHN_CREDENTIALS=YOUR_TOKEN_HERE
- CHROMA_SERVER_CORS_ALLOW_ORIGINS=["*"]
- IS_PERSISTENT=TRUE
- PERSIST_DIRECTORY=/chroma/chroma
healthcheck:
test: ["CMD", "curl", "-f", "http://localhost:8000/api/v1/heartbeat"]
interval: 30s
timeout: 10s
retries: 3Important: The port binding 127.0.0.1:8000:8000 exposes ChromaDB only on localhost. External access goes through Nginx with TLS. Never bind directly to 0.0.0.0 on a production server.
Start ChromaDB
cd ~/chromadb
docker compose up -d
# Check container status
docker compose ps
# Watch logs for startup errors
docker compose logs -f chromadbTest the heartbeat endpoint locally:
curl http://localhost:8000/api/v1/heartbeat
# Expected: {"nanosecond heartbeat": <timestamp>}Configure Nginx as a Reverse Proxy
sudo apt install -y nginx certbot python3-certbot-nginxserver {
listen 80;
server_name your-domain.com;
location / {
proxy_pass http://127.0.0.1:8000;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
# Support large embedding payloads
client_max_body_size 50M;
proxy_read_timeout 120s;
}
}sudo ln -s /etc/nginx/sites-available/chromadb /etc/nginx/sites-enabled/
sudo nginx -t
sudo systemctl reload nginx
# Issue TLS certificate
sudo certbot --nginx -d your-domain.comConfigure the Firewall
sudo ufw allow OpenSSH
sudo ufw allow 'Nginx Full'
sudo ufw enable
# Confirm ChromaDB port is NOT exposed directly
sudo ufw status
# Port 8000 should NOT appear in the listConnect a Client
Test the full HTTPS path with your auth token:
# Test heartbeat over HTTPS
curl -H "Authorization: Bearer YOUR_TOKEN_HERE" \
https://your-domain.com/api/v1/heartbeat
# List collections
curl -H "Authorization: Bearer YOUR_TOKEN_HERE" \
https://your-domain.com/api/v1/collectionsPython client example:
pip install chromadbimport chromadb
from chromadb.config import Settings
client = chromadb.HttpClient(
host="your-domain.com",
port=443,
ssl=True,
headers={"Authorization": "Bearer YOUR_TOKEN_HERE"}
)
# Create a collection
collection = client.create_collection("my-docs")
# Add documents with embeddings
collection.add(
documents=["RamNode is a VPS provider", "ChromaDB is a vector database"],
ids=["doc1", "doc2"]
)
# Query by semantic similarity
results = collection.query(
query_texts=["cloud hosting"],
n_results=2
)
print(results)Backups
ChromaDB stores all data as files under the mounted volume. Stop the container, archive the data directory, and restart:
cd ~/chromadb
# Stop the container cleanly before backup
docker compose stop chromadb
# Create a timestamped archive
tar -czf ~/backups/chromadb-$(date +%Y%m%d-%H%M%S).tar.gz ./data
# Restart
docker compose start chromadbFor automated backups, add this as a cron job or pair it with RamNode block storage for off-VPS redundancy.
Upgrading ChromaDB
cd ~/chromadb
docker compose pull
docker compose up -d
# Verify the new version is running
docker compose exec chromadb chroma --versionAlways check the ChromaDB release notes before upgrading — major versions occasionally include migration steps for the on-disk format.
Troubleshooting
Container exits immediately on startup
Check logs for permission errors: docker compose logs chromadb. Fix with sudo chown -R 1000:1000 ~/chromadb/data
401 Unauthorized errors
Confirm you're passing the Authorization: Bearer <token> header. The token must match CHROMA_SERVER_AUTHN_CREDENTIALS exactly, with no trailing whitespace.
Connection refused on port 8000
Port 8000 is bound to localhost only by design. All external traffic must go through Nginx on port 443. Test locally with localhost:8000.
Nginx returns 502 Bad Gateway
ChromaDB container is likely not running or has crashed. Check: docker compose ps and docker compose restart chromadb
Summary
You now have a production-ready ChromaDB instance with:
- Persistent storage — data survives container restarts and upgrades
- Token authentication — all API requests require a Bearer token
- TLS via Nginx — traffic encrypted with a free Let's Encrypt certificate
- Localhost binding — ChromaDB port not exposed to the internet
- Auto-restart — the unless-stopped policy keeps the service up
ChromaDB integrates directly with LangChain, LlamaIndex, and the OpenAI embeddings API, making this a solid foundation for RAG applications and AI-powered search.
