# PRAXIS โ€” full local stack (proposal ยง8 technology selection) # Postgres 16 (system of record) + Redis Streams (queue) + ChromaDB (vector store) # + FastAPI (api) + LangGraph worker. Ollama runs on the host (gpu/metal access), # reached via host.docker.internal. services: postgres: image: postgres:16 environment: POSTGRES_USER: ${POSTGRES_USER:-praxis} POSTGRES_PASSWORD: ${POSTGRES_PASSWORD:-praxis} POSTGRES_DB: ${POSTGRES_DB:-praxis} ports: - "5432:5432" volumes: - pgdata:/var/lib/postgresql/data healthcheck: test: ["CMD-SHELL", "pg_isready -U praxis"] interval: 5s timeout: 3s retries: 10 restart: unless-stopped logging: driver: json-file options: max-size: "10m" max-file: "3" redis: image: redis:7 command: ["redis-server", "--appendonly", "yes"] ports: - "6379:6379" volumes: - redisdata:/data healthcheck: test: ["CMD", "redis-cli", "ping"] interval: 5s timeout: 3s retries: 10 restart: unless-stopped logging: driver: json-file options: max-size: "10m" max-file: "3" chromadb: image: chromadb/chroma:0.5.20 ports: - "8001:8000" volumes: - chromadata:/chroma/chroma environment: IS_PERSISTENT: "TRUE" ANONYMIZED_TELEMETRY: "FALSE" restart: unless-stopped logging: driver: json-file options: max-size: "10m" max-file: "3" api: build: context: . dockerfile: backend/Dockerfile command: uvicorn api.main:app --host 0.0.0.0 --port 8080 ports: - "8080:8080" environment: PRAXIS_DATABASE_URL: postgresql+psycopg2://${POSTGRES_USER:-praxis}:${POSTGRES_PASSWORD:-praxis}@postgres:5432/${POSTGRES_DB:-praxis} PRAXIS_REDIS_URL: redis://redis:6379/0 PRAXIS_CHROMA_HOST: chromadb PRAXIS_CHROMA_PORT: "8000" PRAXIS_OLLAMA_HOST: http://host.docker.internal:11434 PRAXIS_LLM_PROVIDER: ${PRAXIS_LLM_PROVIDER:-ollama} PRAXIS_LLM_MODEL: ${PRAXIS_LLM_MODEL:-llama3.1:8b} PRAXIS_LLM_FALLBACK_MODELS: ${PRAXIS_LLM_FALLBACK_MODELS:-glm-5.1,minimax-m3} PRAXIS_LLM_LOCAL_MODEL: ${PRAXIS_LLM_LOCAL_MODEL:-llama3.1:8b} PRAXIS_LLM_BASE_URL: ${PRAXIS_LLM_BASE_URL:-https://router-api.0g.ai/v1} ZEROG_COMPUTE_API_KEY: ${ZEROG_COMPUTE_API_KEY:-} PRAXIS_LLM_CLOUD_REQUEST_TIMEOUT: ${PRAXIS_LLM_CLOUD_REQUEST_TIMEOUT:-30} PRAXIS_LLM_CLOUD_TIMEOUT: ${PRAXIS_LLM_CLOUD_TIMEOUT:-90} PRAXIS_LLM_LOCAL_TIMEOUT: ${PRAXIS_LLM_LOCAL_TIMEOUT:-300} PRAXIS_LLM_PROVIDER_SORT: ${PRAXIS_LLM_PROVIDER_SORT:-latency} PRAXIS_COPILOT_REQUEST_TIMEOUT: ${PRAXIS_COPILOT_REQUEST_TIMEOUT:-25} PRAXIS_COPILOT_CLOUD_TIMEOUT: ${PRAXIS_COPILOT_CLOUD_TIMEOUT:-30} PRAXIS_COPILOT_LOCAL_TIMEOUT: ${PRAXIS_COPILOT_LOCAL_TIMEOUT:-30} PRAXIS_DOC_STORE_PATH: /app/data/documents PRAXIS_CORPUS_PATH: /app/data/corpus PRAXIS_EXPORT_PATH: /app/data/exports PRAXIS_ORG_CONFIG_PATH: /app/data/org_config.json volumes: - ./data:/app/data extra_hosts: - "host.docker.internal:host-gateway" depends_on: postgres: condition: service_healthy redis: condition: service_healthy chromadb: condition: service_started restart: unless-stopped logging: driver: json-file options: max-size: "10m" max-file: "3" worker: build: context: . dockerfile: backend/Dockerfile command: python -m ingestion.worker environment: PRAXIS_DATABASE_URL: postgresql+psycopg2://${POSTGRES_USER:-praxis}:${POSTGRES_PASSWORD:-praxis}@postgres:5432/${POSTGRES_DB:-praxis} PRAXIS_REDIS_URL: redis://redis:6379/0 PRAXIS_CHROMA_HOST: chromadb PRAXIS_CHROMA_PORT: "8000" PRAXIS_OLLAMA_HOST: http://host.docker.internal:11434 PRAXIS_LLM_PROVIDER: ${PRAXIS_LLM_PROVIDER:-ollama} PRAXIS_LLM_MODEL: ${PRAXIS_LLM_MODEL:-llama3.1:8b} PRAXIS_LLM_FALLBACK_MODELS: ${PRAXIS_LLM_FALLBACK_MODELS:-glm-5.1,minimax-m3} PRAXIS_LLM_LOCAL_MODEL: ${PRAXIS_LLM_LOCAL_MODEL:-llama3.1:8b} PRAXIS_LLM_BASE_URL: ${PRAXIS_LLM_BASE_URL:-https://router-api.0g.ai/v1} ZEROG_COMPUTE_API_KEY: ${ZEROG_COMPUTE_API_KEY:-} PRAXIS_LLM_CLOUD_REQUEST_TIMEOUT: ${PRAXIS_LLM_CLOUD_REQUEST_TIMEOUT:-30} PRAXIS_LLM_CLOUD_TIMEOUT: ${PRAXIS_LLM_CLOUD_TIMEOUT:-90} PRAXIS_LLM_LOCAL_TIMEOUT: ${PRAXIS_LLM_LOCAL_TIMEOUT:-300} PRAXIS_LLM_PROVIDER_SORT: ${PRAXIS_LLM_PROVIDER_SORT:-latency} PRAXIS_DOC_STORE_PATH: /app/data/documents PRAXIS_CORPUS_PATH: /app/data/corpus PRAXIS_EXPORT_PATH: /app/data/exports PRAXIS_ORG_CONFIG_PATH: /app/data/org_config.json volumes: - ./data:/app/data extra_hosts: - "host.docker.internal:host-gateway" depends_on: postgres: condition: service_healthy redis: condition: service_healthy chromadb: condition: service_started healthcheck: disable: true restart: unless-stopped logging: driver: json-file options: max-size: "10m" max-file: "3" # SSO โ€” demo Keycloak realm (Tier 1). The realm is imported on first boot from # ./data/keycloak/. Validated against THIS test IdP, not a firm's live enterprise AD. keycloak: image: quay.io/keycloak/keycloak:26.0 command: ["start-dev", "--import-realm"] environment: KEYCLOAK_ADMIN: admin KEYCLOAK_ADMIN_PASSWORD: admin KC_DB: dev-file ports: - "8081:8080" volumes: - ./data/keycloak:/opt/keycloak/data/import - keycloakdata:/opt/keycloak/data healthcheck: test: ["CMD-SHELL", "curl -sf http://localhost:8080/realms/praxis/.well-known/openid-configuration || exit 1"] interval: 10s timeout: 5s retries: 30 restart: unless-stopped logging: driver: json-file options: max-size: "10m" max-file: "3" # LDAP โ€” demo OpenLDAP server (Tier 3). Validated against THIS test IdP, not a live AD. openldap: image: osixia/openldap:1.5.0 ports: - "389:389" environment: LDAP_ORGANISATION: "PRAXIS" LDAP_DOMAIN: "praxis.local" LDAP_ADMIN_PASSWORD: "admin" healthcheck: test: ["CMD", "ldapsearch", "-x", "-H", "ldap://localhost:389", "-b", "dc=praxis,dc=local", "-s", "base"] interval: 10s timeout: 5s retries: 5 restart: unless-stopped logging: driver: json-file options: max-size: "10m" max-file: "3" volumes: pgdata: redisdata: chromadata: keycloakdata: