DevOps - LLMSystemTrading

abstract

Deployment architecture, services, environment variables, and operational runbooks for LLMSystemTrading.

Services Map

Docker Services

ServiceImageInternal PortPurpose
trading_redisredis:7-alpine6379Low-latency cache and ephemeral state
trading_postgrespostgres:16-alpine5432OLTP — accounts, trades, strategies, settings
trading_questdbquestdb/questdb:latest9000 / 9009 / 8812Time-series OHLCV candle storage
trading_nginxnginx:alpine80Reverse proxy routing to backend and frontend
trading_backendBuilt from backend/Dockerfile8000FastAPI + Uvicorn application server
trading_frontendBuilt from frontend/Dockerfile3000Next.js 16 SSR frontend

Environment Variables

Backend (.env in project root)

VariableRequiredDescriptionExample
DATABASE_URLYesPostgreSQL async connection stringpostgresql+asyncpg://trading:trading_dev@postgres:5432/trading
POSTGRES_PASSWORDYesPostgreSQL password (compose default: trading_dev)your_secure_password
REDIS_URLYesRedis connection stringredis://redis:6379
QUESTDB_HOSTYesQuestDB hostname (Docker service name)questdb
QUESTDB_PORTYesQuestDB asyncpg wire protocol port8812
LLM_PROVIDERYesActive LLM provideranthropic
ANTHROPIC_API_KEYConditionalRequired if LLM_PROVIDER=anthropicsk-ant-...
OPENAI_API_KEYConditionalRequired if LLM_PROVIDER=openaisk-...
GEMINI_API_KEYConditionalRequired if LLM_PROVIDER=geminiAIza...
OPENROUTER_API_KEYConditionalRequired if LLM_PROVIDER=openroutersk-or-...
DEBUGNoFastAPI debug modeFalse
TELEGRAM_BOT_TOKENNoTelegram alerts bot token (stored encrypted in DB)1234567890:ABC...
TELEGRAM_CHAT_IDNoTelegram chat ID for alerts-100123456789
MAINTENANCE_INTERVAL_MINUTESNoScheduler interval override15

PostgreSQL Container Environment

VariableRequiredDescriptionExample
POSTGRES_DBYesDatabase name (hardcoded in compose)trading
POSTGRES_USERYesDatabase user (hardcoded in compose)trading
POSTGRES_PASSWORDYesDatabase password (from .env)trading_dev
QDB_CAIRO_MAX_UNCOMMITTED_ROWSNoQuestDB batch commit threshold1000

Frontend (frontend/.env.local)

VariableRequiredDescriptionExample
NEXT_PUBLIC_API_URLYesBackend HTTP base URLhttp://localhost:8000
NEXT_PUBLIC_WS_URLNoBackend WebSocket base URLws://localhost:8000
warning

NEXT_PUBLIC_API_URL is baked into the Next.js client bundle at build time (next build). If you change the backend URL, you must rebuild the frontend Docker image — a running container restart is not sufficient.

Nginx Routing Rules

Path PatternProxy TargetNotes
/api/**http://backend:8000All REST API calls
/ws/**ws://backend:8000WebSocket upgrade
/**http://frontend:3000Next.js SSR fallback

Config file: nginx/nginx.conf

warning

The nginx/ directory was not present at inspection time. The nginx service in docker-compose.yml references nginx:alpine but the config mount point was not confirmed. Verify nginx/nginx.conf exists before running docker compose up.

Deployment Procedure

Commands

# Start all services
docker compose up -d
 
# Start with rebuild (after code changes)
docker compose up -d --build
 
# Check status
docker compose ps
 
# View logs (follow mode)
docker compose logs -f backend
docker compose logs -f frontend
 
# Stop (preserves volumes)
docker compose down
 
# Stop and remove volumes (destructive — data loss)
docker compose down -v

Backup & Restore

OperationCommandOutput
Backup PostgreSQLdocker exec trading_postgres pg_dump -U trading trading > backup.sqlbackup.sql
Backup QuestDBCopy questdb_data volume directoryVolume directory
Backup Redisdocker exec trading_redis redis-cli BGSAVE then copy redis_dataVolume directory
Restore PostgreSQLdocker exec -i trading_postgres psql -U trading trading < backup.sql
Restore QuestDBStop container, replace volume contents, restart
Restore RedisStop container, replace volume contents, restart

Security Considerations

ConcernMitigation
Database passwords in compose defaultsPOSTGRES_PASSWORD defaults to trading_dev — override in .env before production
LLM API keys in environmentKeys stored in .env file; .env must be in .gitignore; never commit to git
LLM API keys in databaseStored encrypted via Fernet (core/security.py); displayed masked (sk-...abcd)
Exposed database portsPostgreSQL (5432), Redis (6379), QuestDB (9000/8812/19009) exposed on host — restrict with firewall in production
QuestDB Web UIPort 9000 exposes QuestDB web console with no auth — do not expose publicly
NEXT_PUBLIC varsBaked into frontend bundle — do not put secrets in NEXT_PUBLIC_* variables
nginx no TLSHTTP only in default config — add SSL termination for public deployments

Known Gotchas

warning

Port 9009 is reserved by Windows WSL2. QuestDB InfluxDB protocol uses host port 19009 instead (19009:9009 in compose). Code must connect to host port 19009 for InfluxDB line protocol inserts.

warning

NEXT_PUBLIC_API_URL is embedded into the JavaScript bundle at next build time. Changing it at runtime has no effect. Rebuild the frontend image after changing this variable.

warning

./docker/postgres/init.sql only runs on first container initialization (empty volume). To re-run it, delete the postgres_data volume: docker compose down -v && docker compose up -d.

warning

On backend startup, any runs stuck in running or cancelling state are automatically reset to cancelled. This handles crash recovery but means interrupted long runs cannot be resumed — they must be restarted.

warning

MetaTrader 5 runs natively on Windows. The backend connects via python-mt5 which communicates with the locally-installed MT5 terminal. MT5 cannot run inside Docker.

Technical Defense

question

A: Each solves a different problem. PostgreSQL handles ACID-compliant relational data (accounts, trades, strategies). Redis provides low-latency caching and ephemeral state. QuestDB is a columnar time-series database optimized for OHLCV candle inserts via InfluxDB line protocol — it handles millions of rows far faster than PostgreSQL for time-ordered data.

question

A: pg_dump creates a full SQL backup. QuestDB data lives in a named Docker volume (questdb_data) — copy it to restore. Redis is cache-only; losing it means the next request refills from PostgreSQL. The most critical backup target is postgres_data + questdb_data.

question

A: restart: unless-stopped in docker-compose.yml. All services auto-restart after crash. Set up Docker daemon to start on system boot for true 24/7 operation.

question

A: MT5 runs natively on Windows (it's a Windows application). The backend connects to it via python-mt5 which communicates with the locally-installed MetaTrader 5 terminal. MT5 is NOT containerized.

question

A: Not with the current setup. APScheduler is single-instance; running multiple backend replicas would cause duplicate scheduled jobs. Horizontal scaling would require Celery + RabbitMQ for job coordination and a shared Redis for distributed locking.

question

A: This controls when QuestDB flushes in-memory rows to disk. Lower values reduce data loss on crash; higher values increase throughput. 1000 is a balance for candle insert batches — matches the typical batch size for multi-symbol, multi-timeframe candle fetches.

Change Impact Analysis

If you change…These are affected
PostgreSQL port (5432)DATABASE_URL in .env, all backend DB connection code
Redis port (6379)REDIS_URL in .env, backend cache code
QuestDB port 19009InfluxDB insert code in backend/db/questdb.py
QuestDB port 8812asyncpg query code in backend/db/questdb.py
nginx routing rulesFrontend API base URL, backend CORS config
Add new env variable.env.example, docker-compose.yml, backend core/config.py Settings class
Base Docker image versionDockerfile, requires regression testing
POSTGRES_PASSWORDMust update DATABASE_URL to match; existing data remains intact
NEXT_PUBLIC_API_URLMust rebuild frontend Docker image — runtime change has no effect
Backend core/config.py SettingsUpdate .env.example to document new field; update compose env section
RoleLink
Project Overview[Kanban - LLMSystemTrading](Kanban - LLMSystemTrading)
Backend APIAPI-GET-v1-Status
DB SchemaDB - accounts