Deployment¶
How to run an ml-party server for real. The short version is a decision
tree; every path ends at the same server (mlp serve = web UI + read API +
authenticated sync ingest on one port).
The rule that overrides everything: credentials — passwords, session cookies, API tokens — must never cross plain HTTP beyond localhost. Anything reachable by others goes behind TLS (the
tlsprofile, or your own proxy). And exposing a server beyond a trusted network requires auth mode (users configured): legacy single-token mode leaves the read API open and belongs on trusted networks (SSH tunnel, VPN) only.
Auth: users, roles, tokens¶
Create the first user and the served store switches to authenticated mode —
login required for everything, the legacy --token ignored:
mlp user add paul --role admin --root /srv/mlparty/.mlparty # prompts for a password
mlp user add kim --role viewer --root ... # viewer | writer | admin
mlp token create --user paul --name laptop-sync --root ... # printed ONCE
Roles are store-wide: viewer reads, writer reads + writes
(annotations, sync ingest), admin additionally manages users (CLI).
Humans sign in through the web UI (sessions survive server restarts;
a password reset via mlp user set-password invalidates that user’s
sessions). Machines — training boxes, sync clients, CI — use per-user
tokens: put one in the spool store’s [sync] config exactly like the
legacy token. mlp token revoke <id> cuts one off without touching the
user. A store with no users behaves as before: open reads, single
ML_PARTY_TOKEN gating ingest — fine for localhost and tunnels, nothing
more.
SSO note: the identity layer is built for it (see DESIGN.md §12) — OIDC (Entra ID first) plugs in as a second way to sign in, on top of the same users, roles, and tokens. Until it lands, accounts are per-deployment.
Decision tree¶
Just me, one box →
mlp serve(ormlp uiif you don’t need sync) as a systemd user service; reach it viassh -L 7327:localhost:7327. Template: deploy/mlparty-server.service.A team on a trusted network / VPN → Docker compose + users (binds 127.0.0.1 on the host; expose via the VPN interface or tunnel).
Reachable beyond a trusted network → compose with the
tlsprofile (Caddy, automatic Let’s Encrypt certificates) and users configured — never the legacy token.
Docker¶
docker build -t ml-party .
docker run -d --name mlparty --restart unless-stopped \
-p 127.0.0.1:7327:7327 \
-v mlparty-data:/data \
-e ML_PARTY_TOKEN="$(openssl rand -base64 32)" \
ml-party
# switch it to auth mode (takes effect immediately, no restart):
docker exec -it mlparty mlp user add paul --role admin --root /data
The entrypoint initializes the store on first boot and refuses to start
with no auth at all (neither users on the volume nor ML_PARTY_TOKEN).
The container runs non-root and sees only the /data volume. Health:
docker inspect --format='{{.State.Health.Status}}' mlparty.
Compose¶
export ML_PARTY_TOKEN="$(openssl rand -base64 32)" # or put it in .env
docker compose up -d # localhost:7327 (tunnel access)
DOMAIN=tracker.example.org docker compose --profile tls up -d # public, HTTPS
The tls profile adds Caddy: certificates are obtained and renewed
automatically for $DOMAIN; only ports 80/443 are public and the server
container is not directly exposed. Clients then use
url = "https://tracker.example.org" in their store.toml [sync].
Using nginx instead: terminate TLS as usual and proxy to
127.0.0.1:7327 — but disable response buffering or live metric streams
(SSE) will stall:
location / {
proxy_pass http://127.0.0.1:7327;
proxy_buffering off; # REQUIRED for /api/runs/*/metrics/stream
proxy_set_header Host $host;
}
Backup & restore¶
mlp backup /backups/mlparty-$(date +%F) --root /path/to/store
Safe while runs are writing: the journal is copied first, and since content
(artifacts, git objects) is always written before the journal event that
references it, the snapshot’s journal only references content the copy
contains. A torn trailing journal line (append caught mid-write) is
tolerated on replay. Users and hashed tokens (auth/) are included;
index.sqlite (rebuildable) and sync_state.json (client-side cursors)
are deliberately excluded. Backups contain credential hashes and the
session-signing secret — protect the destination accordingly.
Restore: point at the copy and rebuild the index — that’s all.
mlp rebuild-index --root /backups/mlparty-2026-08-29
mlp serve --root /backups/mlparty-2026-08-29 ...
For the Docker volume, run the same through the image:
docker exec mlparty mlp backup /data-backup --root /data with a second
volume mounted, or plainly rsync the named volume’s directory — the
journal-first ordering is what the mlp backup command exists to get right.
Security model¶
The trust boundaries, the agent-instruction surface (action templates, knowledge-graph content), and vulnerability reporting are summarized in SECURITY.md.
Who needs access to what¶
Only the admin touches Docker/systemd (the docker socket is
root-equivalent — never put ordinary users or service accounts in the
docker group) and manages accounts (mlp user). Everyone else — humans
and agents — interacts with the server exclusively through its URL:
browser login for the UI, a personal [sync] token for shipping runs,
MCP for agents pointed at a spool store.