leankg 0.19.23

Lightweight Knowledge Graph for AI-Assisted Development
# LeanKG Enterprise — separate CozoDB/RocksDB tier.
#
# Two-service compose on a shared bridge network: cozoserver (RocksDB) + leankg
# (MCP/REST API). cozoserver publishes 3000 to host loopback only (127.0.0.1:3000);
# leankg reaches it via host.docker.internal:3000 (the Docker bridge gateway).
# cozo-bin v0.7.6 hardcodes TcpListener::bind("127.0.0.1:3000") so the
# container's loopback must be published to the host for leankg to reach it
# from its own netns (network_mode: service:X is unreliable on Docker Desktop).
#
# Why host.docker.internal: Docker Desktop / Compose does not reliably support
# `network_mode: "service:X"` for namespace sharing. Publishing cozoserver's
# 127.0.0.1:3000 to the host loopback is the simplest bridge for the hardcoded
# cozo-bin bind. skip_auth=true stays safe because cozoserver only binds
# 127.0.0.1 inside its container; Docker forwards that to the host without
# re-binding on 0.0.0.0 internally.
#
# Migration from the single-container `docker-compose.rocksdb.yml`:
#   1. Build the cozoserver image:    docker build -f Dockerfile.cozoserver -t freepeak/cozoserver:latest .
#   2. Build the leankg image:         docker build -f Dockerfile.rocksdb -t freepeak/leankg:latest .
#   3. Start both:                    docker compose -f docker-compose.enterprise.yml up -d
#
# Override host paths, image tags, and resource limits via `.dockerfile`
# (gitignored). See `.dockerfile.example`.

services:
  leankg:
    image: ${LEANKG_IMAGE:-freepeak/leankg:latest}
    build:
      context: .
      dockerfile: Dockerfile.rocksdb
    pull_policy: ${LEANKG_PULL_POLICY:-missing}
    env_file:
      - path: ./.dockerfile
        required: false
    # leankg owns the namespace and publishes MCP + REST UI. Cozoserver joins
    # this namespace below; no extra `networks:` block needed (default bridge
    # is sufficient for the namespace owner to bind 0.0.0.0).
    ports:
      - "9699:9699"   # MCP HTTP/SSE (host-published)
      - "8080:8080"   # REST UI v2    (host-published)
    environment:
      # Published to host loopback (127.0.0.1:3000). leankg reaches it via
      # host.docker.internal:3000 (the Docker bridge gateway). cozo-bin
      # v0.7.6 hardcodes TcpListener::bind("127.0.0.1:3000") regardless of
      # --bind/--port; skip_auth stays true because Docker publishes from
      # the container's 127.0.0.1, not 0.0.0.0.
      # ponytail: port 3000 due to upstream hardcode in cozo-bin/server.rs.
      LEANKG_COZO_ENDPOINT: "http://host.docker.internal:3000"
      MCP_HTTP_PORT: "9699"
      LEANKG_AUTO_INDEX: "1"
      LEANKG_EMBED_ON_BOOT: "0"
      LEANKG_EMBED_BACKGROUND: "0"
      LEANKG_EMBED_FAST: "1"
      LEANKG_EMBED_MODEL: "bge-q"
      LEANKG_EMBED_MAX_SEQ: "128"
      LEANKG_EMBED_MAX_BLOB_CHARS: "500"
      # Lower than the single-container default (512m vs 3072m) because
      # cozoserver now owns the heavy graph state; leankg keeps only the
      # local SQLite + ONNX cache.
      LEANKG_EMBED_MAX_MB: "512"
      LEANKG_EMBED_BACKGROUND_WORKERS: "1"
      LEANKG_EMBED_BACKGROUND_BATCH: "32"
      LEANKG_MMAP_SIZE: "67108864"
      LEANKG_WATCHER_DEBOUNCE_MS: "2000"
      LEANKG_WATCHER_BURST_LIMIT: "256"
      RUST_LOG: "leankg=info"
      LEANKG_SERVE_HTTP: "1"
      LEANKG_SERVE_PORT: "8080"
    volumes:
      # Primary project: the MCP server's working directory + default project.
      - ${HOST_PROJECT_PATH:-./}:${CONTAINER_PROJECT_PATH:-/workspace}
      # Side mounts: extra repos the MCP server auto-indexes on boot.
      # Container paths use neutral defaults (/workspace/other, /workspace/other2).
      # Override both the host-path and container-path vars in .dockerfile
      # (gitignored) to point at your environments. entrypoint.sh's /workspace*
      # glob auto-discovers whatever's mounted here.
      - ${HOST_PROJECT_SIDE_PATH:-${HOST_PROJECT_PATH:-./}}:${CONTAINER_PROJECT_SIDE_PATH:-/workspace/other}
      - ${HOST_PROJECT_SECOND_SIDE_PATH:-${HOST_PROJECT_PATH:-./}}:${CONTAINER_PROJECT_SECOND_SIDE_PATH:-/workspace/other2}
      # Persist ONNX / tokenizer cache across restarts.
      - leankg_models:/root/.cache/leankg
    working_dir: ${CONTAINER_PROJECT_PATH:-/workspace}
    # Smaller envelope than the single-container deploy because RocksDB is
    # offloaded to cozoserver. Single-project LocalEngine KPI (2g) still
    # applies.
    mem_limit: 4g
    mem_reservation: 2g
    cpus: "4"
    pids_limit: 4096
    restart: unless-stopped

  cozoserver:
    # Local-only image — build with `docker build -f Dockerfile.cozoserver`.
    # Operators can swap to a published tag once it ships to a registry.
    image: ${COZOSERVER_IMAGE:-freepeak/cozoserver:latest}
    build:
      context: .
      dockerfile: Dockerfile.cozoserver
    pull_policy: ${COZOSERVER_PULL_POLICY:-missing}
    # Use host networking. cozo-bin v0.7.6 hardcodes
    # TcpListener::bind("127.0.0.1:3000") (see Dockerfile.cozoserver). With
    # host networking, this socket lands on the Docker VM's real loopback,
    # which Docker Desktop correctly exposes to all containers via
    # host.docker.internal. This avoids the flaky port-forwarding path
    # (local-to-localhost TCP forwarding is unreliable on Docker Desktop).
    # No host port clash: cozoserver binds 127.0.0.1:3000 only; leankg binds
    # 0.0.0.0:9699 + 0.0.0.0:8080.
    network_mode: host
    environment:
      COZO_ENGINE: rocksdb
      COZO_PATH: /data/cozo
      # COZO_BIND is intentionally left unset (Dockerfile default = 127.0.0.1).
      # Binding to loopback + sharing leankg's net namespace lets the leankg
      # sidecar reach cozoserver on 127.0.0.1:3000 with skip_auth=true. If
      # you change COZO_BIND to 0.0.0.0, drop network_mode and wire the auth
      # token instead.
      # ponytail: cozo-bin v0.7.6 hardcodes the listening socket at
      # 127.0.0.1:3000 (see Dockerfile.cozoserver). Override COZO_PORT to
      # 3000 here so the LEANKG_COZO_ENDPOINT and healthcheck agree with
      # the actual bind. Remove COZO_PORT=3000 (and use 9070) when
      # upstream fixes the bug.
      COZO_PORT: "3000"
      RUST_LOG: info
    volumes:
      - cozo-data:/data/cozo
    # RocksDB loves RAM (block cache + memtable + bloom filters). 4g is the
    # single-node enterprise floor; tune upward for mega-graphs (>1M nodes).
    # cozoserver does NOT serve external traffic — keep it on the internal
    # `enterprise` network only.
    mem_limit: 4g
    mem_reservation: 2g
    cpus: "4"
    pids_limit: 1024
    restart: unless-stopped
    healthcheck:
      test: ["CMD", "curl", "-fsS", "http://127.0.0.1:3000/"]
      interval: 15s
      timeout: 5s
      start_period: 30s
      retries: 5

volumes:
  cozo-data:
  leankg_models: