Skip to Content
DeploymentDocker Deployment

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

ToolMinimum VersionPurpose
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 .env

2. Configure Environment

Edit .env — all services share a single env file:

Required — must set before first run:

VariableDescription
POSTGRES_USERDatabase username
POSTGRES_PASSWORDDatabase password
POSTGRES_DBDatabase name (default: opentier)
ENCRYPTION_MASTER_KEYAES-256-GCM master key for sealing LLM provider API keys. Generate with: openssl rand -base64 32
FRONTEND_URLYour frontend domain (e.g. https://app.yourdomain.com)

OAuth (enable at least one provider):

VariableDescription
GOOGLE_CLIENT_ID / GOOGLE_CLIENT_SECRETGoogle OAuth 2.0 credentials
GITHUB_CLIENT_ID / GITHUB_CLIENT_SECRETGitHub OAuth App credentials
MICROSOFT_CLIENT_ID / MICROSOFT_CLIENT_SECRETAzure AD OAuth credentials
DISCORD_CLIENT_ID / DISCORD_CLIENT_SECRETDiscord OAuth credentials

Email (SMTP):

VariableDescription
SMTP_HOSTSMTP server (e.g. smtp.gmail.com)
SMTP_USERNAME / SMTP_PASSWORDSMTP credentials
FROM_EMAILSender address

[!NOTE] DATABASE_URL, REDIS_URL, QDRANT_URL, and INTELLIGENCE_SERVICE_URL are 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 with ENCRYPTION_MASTER_KEY.

3. Deploy

docker compose up -d

Docker 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/health

Expected 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:4000

Architecture

Six-Service Stack

ContainerImageRoleInternal PortHost Port
opentier-databasepostgres:18PostgreSQL — transactional data5432—
opentier-redisredis:8-alpineRedis 8 Streams — event bus6379—
opentier-qdrantqdrant/qdrant:latestVector database6333 (HTTP), 6334 (gRPC)—
opentier-intelligenceghcr.io/celestial-0/opentier-intelligence:latestPython gRPC server50051—
opentier-workerghcr.io/celestial-0/opentier-intelligence:latestRedis Streams background workers——
opentier-apighcr.io/celestial-0/opentier-api:latestRust API gateway40004000

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:

VolumeContainerData
postgres_dataopentier-databaseAll PostgreSQL data
qdrant_dataopentier-qdrantAll vector embeddings
redis_dataopentier-redisStream data (AOF persistence)
# Stop services but KEEP all data docker compose down # Stop services and DELETE all data (fresh start) docker compose down -v

Common Operations

View Logs

docker compose logs -f api docker compose logs -f intelligence docker compose logs -f worker docker compose logs -f qdrant

Restart a Single Service

docker compose restart worker docker compose restart intelligence

Database 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 -d

Inspect Qdrant

# Qdrant Web UI (when host port is exposed) open http://localhost:6333/dashboard # Collection stats via API curl http://localhost:6333/collections/knowledge_chunks

Inspect Redis Streams

docker exec -it opentier-redis redis-cli > XLEN ot:intel:ingestion_jobs > XLEN ot:intel:chat_events > XLEN ot:intel:ingestion_jobs:dlq

Troubleshooting

Intelligence Service Fails to Start

Check that Qdrant and PostgreSQL are both healthy:

docker compose logs intelligence | grep ERROR docker compose ps qdrant database

The 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_jobs

If the DLQ is growing, inspect failures:

docker exec -it opentier-redis redis-cli XRANGE ot:intel:ingestion_jobs:dlq - + COUNT 5

Port 4000 Already in Use

# Linux/macOS lsof -i :4000 # Windows netstat -ano | findstr :4000 # Or remap in .env SERVER_PORT=8080
Last updated on