fjx-mgmt deployment recipe
fjx-mgmt is the long-running operator surface for FJX (see openspec/changes/afk-mode/specs/mgmt-service/spec.md). It serves the mobile-first dashboard and the MCP endpoint over the per-project telemetry on disk. It is read-only at MVP — no Forgejo or telemetry mutations.
Image
Built and published by .forgejo/workflows/images.yaml on every v* tag:
- Image:
${REGISTRY}/${OWNER}/fjx-mgmt:${VERSION}(also:latest) - Local build:
just images::build fjx-mgmt - Local smoke:
just images::smoke fjx-mgmt
The Dockerfile is multi-stage. The builder runs deno run -A dev.ts build to produce the Fresh _fresh/ bundle; the runtime stage carries only the deno binary, the source tree, and the prebuilt bundle. It runs as the unprivileged deno user shipped by the base image.
Required configuration
Environment variables (read once at startup by mgmt/lib/config.ts):
Volume mounts
fjx-mgmt reads two things from each project directory:
<project>/fjx.json— project config (schema)<project>/telemetry/*.jsonland<project>/fjx.sqlite— telemetry shards and (optional) index
It writes nothing. Mount the projects directory read-only:
docker run -d --restart=unless-stopped \
--name fjx-mgmt \
-p 8000:8000 \
-v /srv/fjx/projects:/projects:ro \
-e FJX_PROJECTS_DIR=/projects \
${REGISTRY}/${OWNER}/fjx-mgmt:latest
The dashboard renders an empty-state when FJX_PROJECTS_DIR is empty — useful before the first project lands.
Routes
GET /— dashboard (active runs, recent failures, stuck work, tokens today)GET /runs— cross-project runs list with filtersGET /projects/<project>/runs/<run_id>— run detail with event timelineGET /projects/<project>/issues/<id>— issue view (brief + per-role ledgers)POST /mcp— MCP JSON-RPC endpoint (tools:fjx-recent-activity,fjx-failed-runs,fjx-token-usage,fjx-stuck-work)
Authentication
Deferred. The MVP ships with no auth in front of the service. Deploy it on a network that is not reachable from the public internet — a Tailscale tailnet or a VPN-only host. The spec records this as an explicit non-goal (openspec/changes/afk-mode/proposal.md, "Out of scope: auth design for the mgmt service beyond Tailscale-only"). Revisit when adding mutating endpoints.
Upgrades
The service is stateless. Roll forward with a fresh container; the SQLite index and JSONL shards live in the mounted projects directory and survive the swap. Downtime during the swap is the time to pull and restart — measured in seconds for a warm registry.
Healthcheck
GET / returns 200 with HTML once the routes are mounted. A minimal Docker healthcheck:
HEALTHCHECK --interval=30s --timeout=3s --retries=3 \
CMD wget -qO- http://127.0.0.1:8000/ >/dev/null || exit 1