1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
# 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:
interval: 15s
timeout: 5s
start_period: 30s
retries: 5
volumes:
cozo-data:
leankg_models: