Installation
From the registry (fast path)
The published images need no build and no submodule. Use the compose.release.yml overlay and pick a tag in .env:
# `app.localhost` already resolves to 127.0.0.1 in modern browsers — no
# /etc/hosts edit needed. For a custom DOMAIN, point app.<domain> at the host.
docker compose -f docker-compose.yml -f compose.release.yml up -d
open http://app.localhost
TAG=latest tracks the last semver release; set TAG=nightly for the rolling preview, or any specific version (e.g. TAG=v1.2.3).
From source
git submodule update --init --recursive
cp .env.example .env # edit if needed
make up # builds and starts everything
docker compose ps # wait until all services are "healthy"
open http://localhost:3000 # web UI
open http://localhost:8080/docs # OpenAPI surface
For faster iteration without rebuilding the SPA image, run the Vite dev server on the host:
make frontend-install # one-off: install pnpm deps
make frontend-dev # http://localhost:5173 with HMR
When using frontend-dev, make sure CORS_ALLOWED_ORIGINS in .env includes http://localhost:5173.
Hostnames
One knob drives the rest: DOMAIN (default localhost). Everything is reachable on a single origin — the frontend serves the SPA and reverse-proxies the API (/api, /mcp, /ws, /.well-known) to the orchestrator, so there is no CORS to configure and no API host to bake into the image.
| Variable | Purpose |
|---|---|
DOMAIN | Base domain. The app is served on app.${DOMAIN}. |
PUBLIC_SCHEME | http (default) or https. |
PUBLIC_URL | The orchestrator's own origin. Derived from the two above; override only for split hosts. |
MAX_UPLOAD_MB | Upload cap in MB (default 200). Sizes the nginx limit, the orchestrator cap and the New Analysis dialog. |
For a real deployment set DOMAIN=yourcompany.com and PUBLIC_SCHEME=https, then terminate TLS at Traefik or at an upstream proxy.
Services
| Service | Purpose | Port |
|---|---|---|
ghidra-mcp | Ghidra 12.1.4 headless + REST plugin (vendored bethington/ghidra-mcp) | 8089 |
ghidra-bridge | Python sidecar exposing the 209 tool MCP surface over streamable-HTTP | 8081 |
orchestrator | FastAPI + PydanticAI agent, inbox watcher, REST + MCP + A2A + WebSocket | 8080 |
frontend | React + Vite + Tailwind SPA served by nginx | 3000 |
postgres | Postgres 16 + pgvector — jobs, conversations, embeddings | 5432 |
searxng | Self-hosted web search backend for the agent | 8888 |
script-sandbox | Hardened runner for agent-emitted bash / python / jython snippets | internal |
ghidra-bridge shares its network namespace with ghidra-mcp to reach the loopback-bound REST on 127.0.0.1:8089 without an auth token.
ghidra-mcp
extension, you can skip the bundled headless instance and drive your Ghidra
instead — see the Local Ghidra guide
(make local-ghidra).Updating Ghidra / ghidra-mcp
make update-ghidra
Pulls the latest commit of the submodule, rebuilds the ghidra-mcp image and restarts only that service.