Infrastructure
This section covers how to run Qadra---the Docker services, messaging, configuration, and operational concerns.
Docker Stack
Qadra runs as a Docker Compose stack. All services are containerized for consistency across development, staging, and production.
Core Services
| Service | Container | Port | Purpose |
|---|---|---|---|
| nats | qadra-nats | 4222, 8222 | NATS JetStream message bus (Core <-> Agent <-> Gateway) |
| postgres | qadra-postgres | 5432 | PostgreSQL 16 + pgvector for tenant data, embeddings, SPO index |
| mongodb | qadra-mongodb | 27017 | Audit traces, NATS audit log |
| qadra-core | qadra-core | 3000 (internal) | Rust truth layer -- NATS handlers for epistemic, workload, auth, flows |
| qadra-agent | qadra-agent | 8080 (internal) | Python orchestration layer -- LLM calls, research, agentic loops |
| qadra-gateway | qadra-gateway | 4000 | Node.js HTTP gateway -- JWT, CORS, file uploads, routes to NATS |
| qadra-ui | qadra-ui | 8080 (internal) | Open WebUI fork |
| kong | qadra-kong | 8000, 8001 | API Gateway -- JWT validation, rate limiting, routing |
| minio | qadra-minio | 9000, 9001 | S3-compatible object storage (file uploads, avatars, tenant logos) |
| imaginary | qadra-imaginary | 9002 | HTTP-based image processing (resize, crop, format conversion) |
Service Responsibilities:
- nats: The message bus. All inter-service communication flows through NATS request-reply. JetStream enabled for durable streams. Monitoring at
:8222. - postgres: The source of truth. All authoritative state, embeddings (pgvector HNSW), and SPO triples.
- mongodb: Append-only audit logs. Decision trees, request traces, NATS audit trail (
nats_auditcollection). - qadra-core: The truth layer. Stateless Rust application that handles all NATS subjects (
qadra.*). Eight handler groups: epistemic, workload, auth, email, audit, observer, admin, flow. - qadra-agent: The intelligence layer. Python service that handles LLM-driven orchestration, workload execution, agent queries, and document verification.
- qadra-gateway: The HTTP surface. Express server that translates HTTP requests into NATS messages. Handles JWT creation/validation, CORS, file uploads to MinIO, and image processing via Imaginary. Never touches PostgreSQL directly.
- qadra-ui: The legacy frontend. Open WebUI fork with Qadra branding.
- kong: The single entry point for external traffic. JWT validation, rate limiting, routing, SSL termination.
- minio: S3-compatible file storage. Stores avatars, tenant logos, uploaded documents. Gateway uploads directly; metadata recorded in PostgreSQL via NATS.
- imaginary: Image processing microservice. Resizes avatar uploads to 256x256 WebP. Falls back gracefully if unavailable.
Observability Stack
| Service | Container | Port | Purpose |
|---|---|---|---|
| otel-collector | qadra-otel-collector | 4317, 4318, 8889 | OpenTelemetry Collector |
| loki | qadra-loki | 3100 | Log aggregation |
| prometheus | qadra-prometheus | 9090 | Metrics storage (30d retention, 5GB max) |
| grafana | qadra-grafana | 3200 | Visualization and alerting |
| cadvisor | qadra-cadvisor | 8088 | Container metrics |
Why a full observability stack?
AI workloads are opaque by nature. When a task takes 30 seconds, you need to know: Is it waiting for an LLM? Database query? NATS timeout? The observability stack answers these questions:
- Traces (OTel): See the full request path, including LLM calls and database queries
- Metrics (Prometheus): Track throughput, latency percentiles, error rates
- Logs (Loki): Search across all services for debugging
- Dashboards (Grafana): Visualize everything in one place
- Container Metrics (cAdvisor): CPU, memory, network, disk per container
Starting the Stack
# Clone and enter directory
cd qadra
# Copy environment template
cp .env.example .env
# Edit environment variables
vim .env
# Start all services
docker compose up -d
# View logs for a specific service
docker compose logs -f qadra-core
docker compose logs -f qadra-gateway
# Stop services
docker compose down
Environment Variables
Required Variables
# Database
DATABASE_URL=postgres://qadra:password@postgres:5432/qadra
POSTGRES_PASSWORD=your_secure_password
# Authentication
JWT_SECRET=your_jwt_secret_min_32_chars
JWT_REFRESH_SECRET=your_refresh_secret_min_32_chars
# LLM Provider
OPENROUTER_API_KEY=sk-or-v1-...
Optional Variables
# MongoDB (for audit traces)
MONGO_PASSWORD=qadra_traces_dev
# Email (SMTP2GO)
SMTP2GO_API_KEY=api-...
SMTP2GO_SENDER=noreply@qadra.dev
EMAIL_VERIFICATION_URL=http://localhost:5173/verify-email
# File Storage (MinIO)
MINIO_ROOT_USER=qadra
MINIO_ROOT_PASSWORD=qadra_minio_dev
MINIO_ENDPOINT=http://localhost:9000
MINIO_BUCKET=qadra-files
# Image Processing
IMAGINARY_URL=http://localhost:9002
# LLM (additional)
CEREBRAS_API_KEY=csk-...
# Python Agent
EXA_API_KEY=... # Exa neural search for autonomous research
QADRA_LLM_MODEL=google/gemini-3-flash-preview
# Observability
OTEL_ENABLED=true
OTEL_EXPORTER_OTLP_ENDPOINT=http://otel-collector:4317
GRAFANA_PASSWORD=your_grafana_password
# Server
PORT=3000
RUST_LOG=info
Resource Limits
All containers have memory limits to prevent runaway processes from affecting other services.
| Service | Memory Limit | Memory Reserved | Why This Limit |
|---|---|---|---|
| postgres | 2GB | 512MB | Handles embeddings (vector ops are memory-intensive) |
| mongodb | 1GB | 256MB | Audit writes are small; WiredTiger manages its cache |
| qadra-core | 1GB | 256MB | Rust is memory-efficient; mostly handles async I/O |
| qadra-agent | 512MB | 128MB | Python orchestrator; LLM calls are I/O-bound |
| qadra-gateway | 256MB | 64MB | Thin HTTP layer; file uploads stream to MinIO |
| minio | 512MB | 128MB | Object storage; mostly disk I/O |
| imaginary | 256MB | 64MB | Image processing; handles one image at a time |
| nats | 256MB | 64MB | Message bus; JetStream persistence to disk |
| prometheus | 1GB | 256MB | Stores 30 days of metrics locally |
| grafana | 512MB | 128MB | Dashboard rendering is lightweight |
What happens when limits are hit?
- Soft limit (reservation): Guaranteed minimum; service will not be evicted below this
- Hard limit: If exceeded, container is killed and restarted
- Postgres shared_buffers: Set to 25% of memory limit (512MB)
Network Configuration
All services run on the qadra-network Docker network:
networks:
default:
name: qadra-network
Internal service discovery uses container names:
postgres:5432mongodb:27017nats:4222minio:9000imaginary:9000(internal port; host-mapped to 9002)loki:3100otel-collector:4317
NATS Message Bus
NATS is the backbone of Qadra's inter-service communication. All three application containers (Core, Agent, Gateway) connect to NATS.
Architecture
React SPA (:5173)
|
v HTTP
Node.js Gateway (:4000)
|
v NATS request-reply
Rust Core (:3000 internal) <--> Python Agent (:8080 internal)
| |
v v
PostgreSQL / MongoDB LLM Providers (OpenRouter)
Subject Hierarchy
| Subject Pattern | Handler | Purpose |
|---|---|---|
qadra.{tenant}.epistemic.> | Rust Core | KG queries, triples, attribution, SPO |
qadra.{tenant}.workload.do.> | Rust Core | Workload CRUD and lifecycle |
qadra.{tenant}.flow.do.> | Rust Core | Flow CRUD and execution |
qadra.{tenant}.chat.do.> | Rust Core | Conversation CRUD and messaging |
qadra.auth.> | Rust Core | Authentication (login, register, tokens) |
qadra.email.> | Rust Core | Email operations (templates, delivery) |
qadra.audit.> | Rust Core | Audit log queries |
qadra.admin.> | Rust Core | Super admin operations |
qadra.> | Rust Core (observer) | Catch-all to MongoDB nats_audit |
qadra.{tenant}.workload.execute | Python Agent | Full pipeline orchestration |
qadra.{tenant}.agent.query | Python Agent | Agent loop (synthesis, research) |
Monitoring
NATS monitoring is available at http://localhost:8222:
/healthz-- health check/connz-- active connections/subsz-- subscription details/jsz-- JetStream status
Kong API Gateway
Kong is the single entry point for all external traffic. It sits in front of the Gateway and UI.
Why Kong?
- JWT validation: Verify tokens before requests reach the application
- Rate limiting: Protect against abuse without application logic
- Routing: Direct traffic to API vs. UI based on path
- SSL termination: Handle HTTPS at the edge
- Load balancing: Distribute requests across multiple instances
Kong runs in DB-less mode with declarative configuration (no database needed):
Configuration (kong/kong.yml)
_format_version: "3.0"
services:
- name: qadra-gateway
url: http://qadra-gateway:4000
routes:
- name: api-route
paths:
- /api
strip_path: true
- name: qadra-ui
url: http://qadra-ui:8080
routes:
- name: ui-route
paths:
- /
plugins:
- name: jwt
config:
secret_is_base64: false
claims_to_verify:
- exp
- name: rate-limiting
config:
minute: 100
policy: local
Header Injection
Kong validates JWTs and injects headers downstream:
X-User-Email: user@example.com
X-Tenant-Id: uuid
Authorization: Bearer <validated-token>
Observability
Observability is not optional for AI systems. When an agent takes 45 seconds to produce a report, you need to understand where time is spent.
OpenTelemetry Pipeline
Services (Rust Core, Gateway, Agent)
|
v OTLP (gRPC :4317 / HTTP :4318)
OTel Collector
|
+---> Loki (logs)
+---> Prometheus (metrics, :8889)
|
v
Grafana (visualization, :3200)
Configuration files:
deploy/otel/
├── otel-collector-config.yaml # OTLP receivers, processors, exporters
├── prometheus.yml # Scrape targets (otel-collector, cadvisor)
├── grafana-datasources.yml # Loki + Prometheus datasource provisioning
├── grafana-alerting.yml # Alert rules, contact points, notification policies
└── dashboards/
├── qadra-overview.json # OTel pipeline metrics, logs, collector health
├── container-monitoring.json # cAdvisor container CPU/memory/network/disk
├── kg-smile.json # KG-SMILE gate pass/fail + coverage/connectivity, Story 104
└── eval-quality.json # Agent output-quality eval scores per agent vs threshold, Story 94
Accessing Grafana
URL: http://localhost:3200
Username: admin
Password: (from GRAFANA_PASSWORD, default: qadra-grafana)
Pre-configured Dashboards:
- Qadra Overview (
/d/qadra-overview) -- Request rates, error rates, OTel pipeline health - Container Monitoring (
/d/qadra-containers) -- CPU, memory, network, disk per container - KG-SMILE -- KG-SMILE gate pass/fail + coverage/connectivity, fed by
kg_smile.gate_runtracing events (Story 104) - Eval Quality -- Agent output-quality eval scores per agent vs threshold (Story 94), fed by
qadra.evaltracing events
Alert Rules
8 provisioned alerts in Grafana:
| Alert | Condition | Severity |
|---|---|---|
| High Container CPU Usage | >80% for 5m | warning |
| High Container Memory Usage | >1GB for 5m | warning |
| Critical Container Memory Usage | >2GB for 2m | critical |
| Prometheus Scrape Target Down | down for 2m | critical |
| OTel Collector Export Errors | failures for 5m | warning |
| Container Restarted | restart detected | warning |
| High Error Rate in Logs | >10 errors in 5m | warning |
| Low Disk Space | >85% filesystem | warning |
Database Migrations
Migrations are stored in migrations/ and run via sqlx. There are currently 30 migration files (001 through 030).
# Install sqlx-cli if needed
cargo install sqlx-cli
# Run migrations
DATABASE_URL=postgres://qadra:password@localhost:5432/qadra sqlx migrate run
# Create new migration
sqlx migrate add <name>
# Revert last migration
sqlx migrate revert
# Generate offline query data for CI
cargo sqlx prepare
Key Migration Groups:
| Migrations | Domain |
|---|---|
| 001-005 | Foundation: tenants, agents, graphs, SPO index, memory |
| 006-007 | Users and workloads (pipelines, tasks, artifacts) |
| 008-009 | Plugins and renderers |
| 010-016 | Attribution, documents, epistemic metadata, entity aliases, indexes |
| 017-018 | Orchestration patterns, pipeline data forwarding |
| 019-024 | Multi-tenant users, email templates, team invites, file storage, notifications, super admin |
| 025 | Agent flows (visual workflows with execution engine) |
| 026-028 | Agent persona fields, artifact templates, agent persona refactor (drop atomics model) |
| 029 | Conversations (direct agent chat) |
| 030 | Document proposals (human-gated ingestion) |
Health Checks
Gateway Health
# Liveness
curl http://localhost:4000/health
# NATS connectivity
curl http://localhost:4000/health/ready
Container Health
docker compose ps
All containers should show healthy status. Each service has a configured healthcheck in docker-compose.yml.
Backup and Recovery
PostgreSQL Backup
# Backup
docker exec qadra-postgres pg_dump -U qadra qadra > backup.sql
# Restore
docker exec -i qadra-postgres psql -U qadra qadra < backup.sql
MongoDB Backup
# Backup
docker exec qadra-mongodb mongodump --out /backup
# Restore
docker exec qadra-mongodb mongorestore /backup
MinIO Backup
MinIO data is stored in the minio-data Docker volume. For backup, use the mc (MinIO Client) tool or mount the volume externally.
Scaling
Horizontal Scaling
Qadra Core and Gateway are stateless and can be horizontally scaled:
services:
qadra-core:
deploy:
replicas: 3
NATS distributes messages across subscribers automatically.
Database Scaling
For production:
- Use managed PostgreSQL (RDS, Cloud SQL)
- Enable read replicas for query distribution
- Use connection pooling (PgBouncer)