Docker Deployment
OpenTier’s entire server stack is orchestrated through a single docker-compose.yml in server/. All service images are pulled from GitHub Container Registry (GHCR) — no local build required.
Prerequisites
| Tool | Minimum Version | Purpose |
|---|---|---|
| Docker | 24.0+ | Container runtime |
| Docker Compose | 2.20+ (V2) | Multi-container orchestration |
Quick Start
1. Fetch Compose File & Environment Template
curl -fsSL https://raw.githubusercontent.com/Celestial-0/OpenTier/main/server/docker-compose.yml -o docker-compose.yml
curl -fsSL https://raw.githubusercontent.com/Celestial-0/OpenTier/main/server/.env.example -o .env.example
cp .env.example .env2. Configure Environment
Edit .env — all services share a single env file:
Required — must set before first run:
| Variable | Description |
|---|---|
POSTGRES_USER | Database username |
POSTGRES_PASSWORD | Database password |
POSTGRES_DB | Database name (default: opentier) |
ENCRYPTION_MASTER_KEY | AES-256-GCM master key for sealing LLM provider API keys. Generate with: openssl rand -base64 32 |
FRONTEND_URL | Your frontend domain (e.g. https://app.yourdomain.com) |
OAuth (enable at least one provider):
| Variable | Description |
|---|---|
GOOGLE_CLIENT_ID / GOOGLE_CLIENT_SECRET | Google OAuth 2.0 credentials |
GITHUB_CLIENT_ID / GITHUB_CLIENT_SECRET | GitHub OAuth App credentials |
MICROSOFT_CLIENT_ID / MICROSOFT_CLIENT_SECRET | Azure AD OAuth credentials |
DISCORD_CLIENT_ID / DISCORD_CLIENT_SECRET | Discord OAuth credentials |
Email (SMTP):
| Variable | Description |
|---|---|
SMTP_HOST | SMTP server (e.g. smtp.gmail.com) |
SMTP_USERNAME / SMTP_PASSWORD | SMTP credentials |
FROM_EMAIL | Sender address |
[!NOTE]
DATABASE_URL,REDIS_URL,QDRANT_URL, andINTELLIGENCE_SERVICE_URLare pre-configured for the internal Docker network — do not change them unless deploying outside Docker Compose.
[!IMPORTANT] LLM / AI model API keys are not set in
.env. After the stack is running, configure providers and their API keys via the Admin Dashboard (/dashboard → Admin → Models). Keys are sealed at rest withENCRYPTION_MASTER_KEY.
3. Deploy
docker compose up -dDocker will pull all 6 service images from GHCR and start them in dependency order. The API will be available at http://localhost:4000.
4. Verify
# Check all containers are running
docker compose ps
# API health
curl http://localhost:4000/healthExpected log lines indicating a healthy stack:
opentier-database | database system is ready to accept connections
opentier-redis | Ready to accept connections
opentier-qdrant | Qdrant gRPC listening on 0.0.0.0:6334
opentier-intelligence | gRPC server started on port 50051
opentier-worker | worker runtime started (4 consumers)
opentier-api | 🚀 API Gateway listening on http://0.0.0.0:4000Architecture
Six-Service Stack
| Container | Image | Role | Internal Port | Host Port |
|---|---|---|---|---|
opentier-database | postgres:18 | PostgreSQL — transactional data | 5432 | — |
opentier-redis | redis:8-alpine | Redis 8 Streams — event bus | 6379 | — |
opentier-qdrant | qdrant/qdrant:latest | Vector database | 6333 (HTTP), 6334 (gRPC) | — |
opentier-intelligence | ghcr.io/celestial-0/opentier-intelligence:latest | Python gRPC server | 50051 | — |
opentier-worker | ghcr.io/celestial-0/opentier-intelligence:latest | Redis Streams background workers | — | — |
opentier-api | ghcr.io/celestial-0/opentier-api:latest | Rust API gateway | 4000 | 4000 |
The worker and intelligence containers share the same image; WORKER_ROLE=all (set by Compose) switches the entry point to worker.py.
Startup Sequence
Data Persistence
Named Docker volumes persist across restarts:
| Volume | Container | Data |
|---|---|---|
postgres_data | opentier-database | All PostgreSQL data |
qdrant_data | opentier-qdrant | All vector embeddings |
redis_data | opentier-redis | Stream data (AOF persistence) |
# Stop services but KEEP all data
docker compose down
# Stop services and DELETE all data (fresh start)
docker compose down -vCommon Operations
View Logs
docker compose logs -f api
docker compose logs -f intelligence
docker compose logs -f worker
docker compose logs -f qdrantRestart a Single Service
docker compose restart worker
docker compose restart intelligenceDatabase Operations
# Connect to PostgreSQL directly
docker exec -it opentier-database psql -U opentier -d opentier
# Roll back the last migration
docker compose run --rm api sqlx migrate revert \
--source migrations \
--database-url "postgres://opentier:password@database:5432/opentier?sslmode=disable"
# Reset everything (nuclear)
docker compose down -v && docker compose up -dInspect Qdrant
# Qdrant Web UI (when host port is exposed)
open http://localhost:6333/dashboard
# Collection stats via API
curl http://localhost:6333/collections/knowledge_chunksInspect Redis Streams
docker exec -it opentier-redis redis-cli
> XLEN ot:intel:ingestion_jobs
> XLEN ot:intel:chat_events
> XLEN ot:intel:ingestion_jobs:dlqTroubleshooting
Intelligence Service Fails to Start
Check that Qdrant and PostgreSQL are both healthy:
docker compose logs intelligence | grep ERROR
docker compose ps qdrant databaseThe ensure_collection() call on startup will fail if Qdrant is not reachable.
Worker Not Processing Jobs
Check Redis connectivity and stream depth:
docker compose logs worker | tail -50
docker exec -it opentier-redis redis-cli XLEN ot:intel:ingestion_jobsIf the DLQ is growing, inspect failures:
docker exec -it opentier-redis redis-cli XRANGE ot:intel:ingestion_jobs:dlq - + COUNT 5Port 4000 Already in Use
# Linux/macOS
lsof -i :4000
# Windows
netstat -ano | findstr :4000
# Or remap in .env
SERVER_PORT=8080