Production Deployment
deploy/compose.yaml is the reference single-node deployment. It runs
Postgres, Redis, Qdrant, MinIO, LiteLLM, and (optionally) local embedding/rerank
model servers as one Docker Compose stack, with the Ragz API and Celery workers
alongside it. This page is the checklist for taking that stack from a laptop
demo to something you can point real users at.
Single-node, by design
This is a reference deployment for one host. It scales a long way — see Scaling — but if you need multi-node HA from day one, treat this page as the baseline configuration to adapt, not a drop-in cluster.
Before exposing anything beyond localhost
Every service in deploy/compose.yaml ships with a working dev credential
so docker compose up works out of the box. None of them are safe to expose.
Change all of the following before anyone outside your laptop can reach the
stack:
| Credential | Where it lives | Notes |
|---|---|---|
LITELLM_MASTER_KEY | litellm service env | Admin key for the LiteLLM proxy |
LITELLM_SALT_KEY | litellm service env | Encrypts provider keys at rest in LiteLLM's own DB |
| Postgres password | postgres service env (POSTGRES_PASSWORD) | Shared by Ragz and LiteLLM's database |
| MinIO root user/password | minio service env (MINIO_ROOT_USER / MINIO_ROOT_PASSWORD) | Object storage for documents and extracted blocks |
RAGZ_BOOTSTRAP_PASSWORD | backend bootstrap env | First superadmin's password — 12+ characters |
None of these are read from .env files checked into the repo — set them as
real environment variables (or secrets in your orchestrator) at deploy time.
Back up two things
Before you go live, make sure both of these are captured by your backup job:
- The
pgdatavolume — all tenant data: organizations, workspaces, users, documents metadata, chat history, and encrypted secrets. - The KEK file (
backend/data/ragz_kek, or whereverRAGZ_KEK_FILEpoints). This is the Key Encryption Key that every provider secret is wrapped under (AES-256-GCM envelope encryption). Losing it makes every stored provider key unrecoverable — not "hard to recover," mathematically unrecoverable. See Backups for the full procedure.
Put it behind a reverse proxy with TLS
Every service in deploy/compose.yaml binds to 127.0.0.1 on the host —
Postgres on 55432, Redis on 56379, Qdrant on 56333, MinIO on 59000/
59001, LiteLLM on 54000, and so on. That's deliberate: nothing in the
stack is meant to be reachable directly. The only thing that should be public
is a reverse proxy terminating TLS in front of the Ragz API and web app.
- Run nginx, Caddy, or Traefik in front of the FastAPI backend and the frontend build.
- Terminate TLS at the proxy (Let's Encrypt via Caddy or certbot is the simplest path).
- Pass the real client IP. The backend's rate limiting (auth and chat
endpoints) keys off the request IP — if your proxy doesn't forward
X-Forwarded-For(or your proxy config doesn't trust/parse it), every request looks like it comes from the proxy itself and rate limiting degrades to one shared bucket for all your users.
# Minimal nginx example — adapt to your proxy of choice
location / {
proxy_pass http://127.0.0.1:8000;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
proxy_set_header Host $host;
}If you're fronting the proxy itself with Cloudflare (Tunnel or DNS-proxied), see Cloudflare — the same "real client IP" concern applies at that layer too.
Scale as load grows
The two things that need more headroom as usage grows are ingestion and chat:
- More Celery workers for ingestion throughput (parsing, chunking, embedding). Add workers rather than making one worker do more — see Scaling for the exact command.
- More uvicorn processes for chat/API concurrency.
Move Postgres to managed
deploy/compose.yaml's postgres service is fine for evaluation and small
deployments, but for real traffic move to a managed Postgres instance (RDS,
Cloud SQL, or equivalent) with its own automated backups and point-in-time
recovery. Point DATABASE_URL at it and drop the postgres service from your
compose file. LiteLLM shares the same Postgres instance (a separate litellm
database, seeded by postgres-init.sql) — migrate both together.
Checklist
- Rotated
LITELLM_MASTER_KEY,LITELLM_SALT_KEY, Postgres password, MinIO root user/password - Set a strong
RAGZ_BOOTSTRAP_PASSWORD(12+ characters) - Backup job covers the
pgdatavolume - Backup job covers the KEK file, stored somewhere independent of the Postgres backup
- Reverse proxy terminates TLS; only the proxy is publicly reachable
- Proxy forwards
X-Forwarded-Forand the backend trusts it - A plan exists for adding Celery workers / uvicorn processes under load
- Postgres is on a managed instance with its own backups (or you've accepted the operational cost of running it yourself)