version: "3.8" services: api: build: ./services/api-rs container_name: gctrl-api ports: - "4000:4000" environment: - NODE_ENV=development - PHOENIX_OTLP_URL=${PHOENIX_OTLP_URL:-} - PHOENIX_SERVICE_NAME=gctrl-api - PORT=4000 - RUST_LOG=${RUST_LOG:-info,gctrl_api=debug} - DATABASE_URL=postgresql://GCTRL:GCTRL@postgres:5432/GCTRL - REDIS_URL=redis://redis:6379 - NEO4J_URI=bolt://neo4j:7687 - NEO4J_USER=neo4j - NEO4J_PASSWORD=password - JWT_SECRET=${JWT_SECRET:-GCTRL-dev-jwt-secret-change-in-production} - JWT_REFRESH_SECRET=${JWT_REFRESH_SECRET:-GCTRL-dev-refresh-secret-change-in-production} # AES-256-GCM key (base64 of 32 bytes) for at-rest secret encryption. # MUST be set & rotated in prod; losing it makes encrypted secrets unrecoverable. - GCTRL_SECRET_KEY=${GCTRL_SECRET_KEY:-rjFL483r7bwkbIpLSqYokmRqmZvq4Jt0XHUrEqczQ9g=} - MAIL_HOST=maildev - MAIL_PORT=1025 # Origin the user's browser actually hits. The dockerized web (nginx) is # published on 3001 below, so the OAuth redirect_uri sent to Google and the # post-login redirect must point there too. Override via .env for prod/dev. - FRONTEND_URL=${FRONTEND_URL:-http://localhost:3001} - KEX_WORKER_URL=http://kex:4010 - FUSE_URL=http://fuse:4020 - INTERNAL_API_SECRET=${INTERNAL_API_SECRET:-gctrl-dev-internal-secret-change-me} - QDRANT_URL=http://qdrant:6333 - QDRANT_API_KEY=${QDRANT_API_KEY:-} - OLLAMA_BASE=http://ollama:11434 # Cloak Gateway cloud upstreams (default: the official hosts, pinned). # - ANTHROPIC_BASE=https://api.anthropic.com # - OPENAI_BASE=https://api.openai.com # Skips the official-host pin on those bases. Dev only, never in prod. # - GCTRL_CLOAK_UPSTREAM_UNPINNED=1 # OAuth Connectors (set in .env or override here) - GCTRL_BASE_URL=${GCTRL_BASE_URL:-http://localhost:4000} # Root under which server-side Obsidian folder vaults must live (read-only mount below). - GCTRL_VAULTS_ROOT=/vaults # Remote MCP gateway (/api/agent/mcp) — lets external multi-agent # orchestrators drive the Pi harness over the network. OFF by default; # every call is api-key authed, clearance-scoped and audited. On by default # so the MCP gateway is reachable right after install; set to false to disable. - GCTRL_AGENT_GATEWAY_ENABLED=${GCTRL_AGENT_GATEWAY_ENABLED:-true} # ── Single source of truth for user-visible endpoint URLs ────────────── # The UI fetches GET /api/config/public and derives EVERY displayed URL # (MCP/n8n setup strings, Neo4j Browser links, agent health) from one # canonical external origin = FRONTEND_URL (above). Change the published # ports + FRONTEND_URL here / in .env and all displayed references update # automatically — no hardcoded host:port is baked into the web bundle. # The two vars below override the auto-derived Neo4j Browser / agent URLs. # Leave empty to let the frontend derive http://:7474 / :7070. - GCTRL_NEO4J_BROWSER_URL=${GCTRL_NEO4J_BROWSER_URL:-} - GCTRL_AGENT_URL=${GCTRL_AGENT_URL:-} # A4 memory-maintenance worker cadence (seconds). Default 600 (10 min). - GCTRL_MEMORY_TICK_SECS=${GCTRL_MEMORY_TICK_SECS:-600} - GOOGLE_CLIENT_ID=${GOOGLE_CLIENT_ID:-} - GOOGLE_CLIENT_SECRET=${GOOGLE_CLIENT_SECRET:-} - MICROSOFT_CLIENT_ID=${MICROSOFT_CLIENT_ID:-} - MICROSOFT_CLIENT_SECRET=${MICROSOFT_CLIENT_SECRET:-} - SLACK_CLIENT_ID=${SLACK_CLIENT_ID:-} - SLACK_CLIENT_SECRET=${SLACK_CLIENT_SECRET:-} - GITHUB_CLIENT_ID=${GITHUB_CLIENT_ID:-} - GITHUB_CLIENT_SECRET=${GITHUB_CLIENT_SECRET:-} depends_on: postgres: condition: service_healthy redis: condition: service_started volumes: # Expose operator-chosen vault folders read-only. The API reads .md bytes # from here for server-side Obsidian folder vaults (under GCTRL_VAULTS_ROOT). - ${GCTRL_VAULTS_ROOT_HOST:-./vaults}:/vaults:ro # The in-dashboard "Update now" button drives docker via this socket # (pull + recreate). Without it the update executor can't reach docker # (matches deploy/compose-template.yml, which already mounts it). - /var/run/docker.sock:/var/run/docker.sock:ro # Host hardware profile (true host RAM/GPU). Host RAM is unknowable from # inside the container (/proc only sees the WSL2 VM), so it's supplied here. # WITHOUT this mount the file is lost on every container recreate and the # Settings "System (host)" view + Re-scan go blank. See deploy/hardware.json. - ./deploy/hardware.json:/app/hardware.json:ro networks: - GCTRL - ollama-net restart: unless-stopped web: build: ./services/web container_name: gctrl-web ports: - "3001:80" depends_on: - api networks: - GCTRL restart: unless-stopped # gctrl-agent: license enforcement — disabled for local dev. # Re-enable for prod testing by pulling ghcr.io/gctrl-tech/agent:latest # after `docker login ghcr.io`. kex: build: ./services/kex container_name: gctrl-kex ports: - "127.0.0.1:4010:4010" environment: - OLLAMA_BASE=http://ollama:11434 - NEO4J_URI=bolt://neo4j:7687 - NEO4J_USER=neo4j - NEO4J_PASSWORD=password - REDIS_URL=redis://redis:6379 - QDRANT_URL=http://qdrant:6333 - QDRANT_API_KEY=${QDRANT_API_KEY:-} - QDRANT_COLLECTION=GCTRL_chunks - EMBEDDING_MODEL=nomic-embed-text - PG_URL=postgresql://GCTRL:GCTRL@postgres:5432/GCTRL - PORT=4010 - INTERNAL_API_SECRET=${INTERNAL_API_SECRET:-gctrl-dev-internal-secret-change-me} # Worker credential fetch for keyed OpenAI-compatible runtimes (D7 in # docs/superpowers/specs/2026-09-04-mlx-runtime-and-backpressure-design.md). - GCTRL_INTERNAL_API_URL=http://api:4000 - KEX_ENTITY_VERIFY=${KEX_ENTITY_VERIFY:-false} - PHOENIX_OTLP_URL=${PHOENIX_OTLP_URL:-} - PHOENIX_SERVICE_NAME=gctrl-kex - GLINER_MODEL=${GLINER_MODEL:-} - GLINER_FALLBACK_MODEL=${GLINER_FALLBACK_MODEL:-} - NER_THRESHOLD_BI=${NER_THRESHOLD_BI:-} volumes: - kex-models:/app/models depends_on: redis: condition: service_started postgres: condition: service_healthy networks: - GCTRL - ollama-net restart: unless-stopped fuse: build: ./services/fuse container_name: gctrl-fuse ports: - "127.0.0.1:4020:4020" environment: - NEO4J_URI=bolt://neo4j:7687 - NEO4J_USER=neo4j - NEO4J_PASSWORD=password - REDIS_URL=redis://redis:6379 - PORT=4020 - SIMILARITY_THRESHOLD=0.85 - RESOLVER_URL=http://resolver:8080 # WIKI distillation: Postgres (wiki_pages), Qdrant (grounding chunks), # Ollama (page generation). Provider/model configurable; defaults are # zero-config local Ollama llama3.2. - PG_URL=postgresql://GCTRL:GCTRL@postgres:5432/GCTRL - QDRANT_URL=http://qdrant:6333 - QDRANT_API_KEY=${QDRANT_API_KEY:-} - QDRANT_COLLECTION=GCTRL_chunks - OLLAMA_BASE=http://ollama:11434 - INTERNAL_API_SECRET=${INTERNAL_API_SECRET:-gctrl-dev-internal-secret-change-me} - GCTRL_INTERNAL_API_URL=http://api:4000 - GCTRL_DISTILL_PROVIDER=${GCTRL_DISTILL_PROVIDER:-ollama} - GCTRL_DISTILL_MODEL=${GCTRL_DISTILL_MODEL:-llama3.2} - PHOENIX_OTLP_URL=${PHOENIX_OTLP_URL:-} - PHOENIX_SERVICE_NAME=gctrl-fuse depends_on: redis: condition: service_started postgres: condition: service_healthy networks: - GCTRL - ollama-net restart: unless-stopped resolver: build: context: ../DataBorg Github/LIMES-master dockerfile: Dockerfile container_name: gctrl-resolver ports: - "8085:8080" environment: - JDK_JAVA_OPTIONS=-Xmx2G command: ["-s"] networks: - GCTRL restart: unless-stopped postgres: image: postgres:16-alpine container_name: gctrl-postgres ports: - "5433:5432" environment: - POSTGRES_USER=GCTRL - POSTGRES_PASSWORD=GCTRL - POSTGRES_DB=GCTRL volumes: - GCTRL-pgdata:/var/lib/postgresql/data - ./shared/db/migrations:/docker-entrypoint-initdb.d healthcheck: test: ["CMD-SHELL", "pg_isready -U GCTRL"] interval: 5s timeout: 5s retries: 5 networks: - GCTRL restart: unless-stopped redis: image: redis:7-alpine container_name: gctrl-redis ports: - "6380:6379" networks: - GCTRL restart: unless-stopped maildev: image: maildev/maildev container_name: gctrl-maildev ports: - "1081:1080" - "1026:1025" networks: - GCTRL restart: unless-stopped volumes: GCTRL-pgdata: kex-models: networks: GCTRL: driver: bridge ollama-net: external: true name: self-hosted-ai-starter-kit_demo