# Shared MemPalace server (optional) — one palace for many clients. # # Runs `mempalace-mcp` over HTTP so several containers/harnesses (pi + # opencode + native) can share ONE palace instead of each keeping its own. # Point every client at it by setting, in that client's .env: # # MEMPALACE_REMOTE_URL=http://:8765/mcp # MEMPALACE_REMOTE_TOKEN= # # (see .env.example). When set, the client connects over HTTP and does NOT # spawn its own local mempalace-mcp. # # Start: docker compose -f docker-compose.mempalace.yml up -d # Stop: docker compose -f docker-compose.mempalace.yml down # Logs: docker compose -f docker-compose.mempalace.yml logs -f # # Why reuse the devbox image? mempalace-mcp is already installed in it, and # reusing it GUARANTEES the server's mempalace version matches the clients' # (both are pinned by the same image build). Override with a slimmer image via # MEMPALACE_SERVER_IMAGE if you prefer (it must provide `mempalace-mcp`). # # ⚠ SECURITY: the HTTP transport IS authenticated as of mempalace 3.6.0 — an # earlier version of this comment said otherwise and was wrong. The server # compares `Authorization: Bearer ` with hmac.compare_digest and # **refuses to start on a non-loopback bind without a token**, so # MEMPALACE_REMOTE_TOKEN below is required, not optional: without it this # service crash-loops. It also pins `Host` and allowlists `Origin`. # # Still do not publish port 8765 to an untrusted network. The default binds to # 127.0.0.1 (host loopback) only. To let sibling containers reach it, attach # them to the shared `mempalace-net` network (container-to-container, no host # port needed — use http://mempalace-server:8765/mcp). To reach it from # elsewhere, terminate TLS in a tunnel/reverse proxy and let the bearer token be # the authentication — do NOT add browser-shaped auth (SSO/PIN/password) in # front, because every MCP client here is a headless JSON-RPC POST and would # receive a login page where JSON should be. name: mempalace-server services: mempalace: image: ${MEMPALACE_SERVER_IMAGE:-joakimp/pi-devbox:latest} container_name: mempalace-server # Bypass the devbox entrypoint (dev-shell/LAN/config setup) and run the # HTTP MCP server directly. HOME + explicit --palace pin the data path so # it does not depend on the image's default user/HOME. Runs as root so it # can initialise the fresh named volume; the volume is dedicated to this # server (clients reach it over HTTP, never by mounting it). entrypoint: [] user: "0:0" environment: - HOME=/data # Required: mempalace refuses a non-loopback bind without a token (it # would exit at startup and, with restart:unless-stopped, crash-loop). # `:?` fails fast at `docker compose up` with a readable message instead. # Clients send the same value as MEMPALACE_REMOTE_TOKEN. - MEMPALACE_MCP_HTTP_TOKEN=${MEMPALACE_REMOTE_TOKEN:?set MEMPALACE_REMOTE_TOKEN in .env — the shared palace requires a bearer token} command: - mempalace-mcp - --transport - http - --host - "0.0.0.0" - --port - "8765" - --palace - /data/.mempalace restart: unless-stopped # Loopback-only by default (see SECURITY note). Use "8765:8765" to expose on # all host interfaces, or drop `ports:` entirely and rely on mempalace-net. ports: - "127.0.0.1:8765:8765" volumes: # The shared palace data — precious; back this up. - mempalace-shared:/data/.mempalace # Embedding-model cache (~79 MB, disposable) so search does not re-download. - mempalace-shared-chroma:/data/.cache/chroma # Transcript inbox. Clients cannot mine into a remote palace directly: # `mempalace_mine` expands its source path in THIS process, so it can only # see paths inside this container. Each client rsyncs its staged session # exports to a per-device subdirectory on the host (see # MEMPALACE_PI_SSH_TARGET in .env.example) and then calls mempalace_mine # with the container-side path below (MEMPALACE_PI_REMOTE_PATH=/data/feed). # Read-only: mining only reads sources, and all locks live palace-side. - ${MEMPALACE_FEED_DIR:-./feed}:/data/feed:ro networks: - mempalace-net healthcheck: # GET /healthz, which is Host/Origin-gated but deliberately token-free — # so this probe needs no credentials. Do NOT go back to POSTing # `tools/list` here: that carries no Authorization header and now 401s, # marking a perfectly healthy server unhealthy forever. The Host pin is # only enforced on loopback *binds* (this one is 0.0.0.0), so a request to # 127.0.0.1 inside the container passes. test: - CMD - python3 - -c - "import urllib.request,sys; sys.exit(0 if urllib.request.urlopen('http://127.0.0.1:8765/healthz',timeout=5).status==200 else 1)" interval: 30s timeout: 10s retries: 3 start_period: 60s volumes: mempalace-shared: mempalace-shared-chroma: networks: mempalace-net: name: mempalace-net