12 KiB
MemPalace on Unraid — GUI install with br0 networking
This walks through installing MemPalace as a single Docker container on
Unraid, using the dockerMan WebUI (no compose, no SSH-driven YAML),
attached to br0 so the container has its own IP address on your
LAN — separate from Unraid's IP.
If you'd rather do an SSH/compose install with TLS + bearer auth via a
Caddy sidecar, see README.md. This guide is the
GUI-friendly path.
Why br0?
br0 (macvlan-style bridge to your physical network) gives the
container its own LAN IP. That means:
- No port conflicts with Unraid itself (you can keep
8765/8766instead of remapping). - Clients connect to the container directly —
http://<container-ip>:8765/sserather thanhttp://<unraid-ip>:8765/sse. - The container can be reached by every machine on your LAN without punching holes in the Unraid host.
The trade-off, classic macvlan:
- By default the Unraid host itself can't talk to the container. If you only ever connect from other machines, this is fine. If you also want to test from Unraid's own shell, enable Host access to custom networks (see Step 1).
- The container is exposed bare to the LAN. Anyone on the LAN can hit the MCP endpoint — it has no auth in this mode. See "Security notes" at the bottom.
Prerequisites
- Unraid 6.12+ with Docker enabled.
- The repo synced to a path on Unraid (e.g.
/mnt/user/system/build/mempalace). You can clone over SSH or just copy the folder via SMB. /mnt/user/appdataavailable (default on every Unraid).- A free LAN IP for the container, ideally with a DHCP reservation on your router so it doesn't drift.
Step 1 — Enable br0 in Unraid
Skip this if you already use br0 for other containers.
- Stop the Docker service so settings are editable: Settings → Docker → Enable Docker = No → Apply.
- Settings → Docker → Advanced View.
- Host access to custom networks = Enabled. (Lets the Unraid
shell reach
br0containers — needed forcurlhealth checks from Unraid itself. Skip if you don't need that.) - Docker custom network type = macvlan (default) or ipvlan if your switch/router doesn't like macvlan MAC churn.
- Re-enable Docker: Enable Docker = Yes → Apply.
- Confirm
br0shows up under Settings → Docker → Docker custom networks (or implicitly — every Unraid with a bridge has it).
Step 2 — Build the image on Unraid
There's no public registry image — the container is built locally from
the repo's root Dockerfile. SSH into Unraid (or open the WebUI
terminal) and run:
cd /mnt/user/system/build/mempalace # wherever you put the repo
docker build -t mempalace-server:latest .
First build pulls python:3.13-slim and pip-installs mempalace +
mcp-proxy (~3–5 minutes). When it finishes:
docker images mempalace-server
# REPOSITORY TAG IMAGE ID CREATED SIZE
# mempalace-server latest <hash> <time> ~600 MB
You only repeat this step when you git pull an update.
Step 3 — Create appdata directories
mkdir -p /mnt/user/appdata/mempalace
chown -R 99:100 /mnt/user/appdata/mempalace
The container runs as UID/GID 99:100 (nobody:users — the Unraid
appdata convention). Wrong ownership here is the #1 cause of "permission
denied" on first start.
Step 4 — Add the container in the WebUI
-
Docker → Add Container (don't pick a template — fill it in manually).
-
Fill the top section:
Field Value Name MemPalaceRepository mempalace-server:latestNetwork Type Custom: br0 Fixed IP address (your chosen LAN IP, e.g. 192.168.1.50)Console shell command BashPrivileged OffExtra Parameters --user 99:100Use a fixed IP. macvlan + DHCP can race the DHCP server on container restart and end up unreachable.
-
Leave WebUI blank (there isn't one). Icon URL optional:
https://raw.githubusercontent.com/MemPalace/mempalace/develop/assets/mempalace_logo.png
Step 5 — Add the path mapping
Click Add another Path, Port, Variable, Label or Device → Path:
| Field | Value |
|---|---|
| Name | Appdata |
| Container Path | /data |
| Host Path | /mnt/user/appdata/mempalace |
| Access Mode | Read/Write |
This holds ChromaDB, the knowledge graph, the embedding-model cache, and the inbox for ingested transcripts. Lose it = lose all memory.
Step 6 — Add the ports
In br0 mode the "host port" field is just a label — the container's
ports are published on the container's own IP, not Unraid's. Set
both to match the container port.
Click Add another … → Port twice:
| Name | Container Port | Host Port | Connection Type |
|---|---|---|---|
MCP SSE |
8765 |
8765 |
TCP |
Ingest HTTP |
8766 |
8766 |
TCP |
Skip the ingest port if you only use this from a single machine and won't push transcripts in via HTTP hooks.
Step 7 — Add environment variables
Click Add another … → Variable for each:
Required
| Name | Key | Value |
|---|---|---|
| Palace path | MEMPALACE_PALACE_PATH |
/data/palace |
Recommended (enables transcript ingest)
| Name | Key | Value |
|---|---|---|
| Ingest port | MEMPALACE_INGEST_PORT |
8766 |
| Ingest bind | MEMPALACE_INGEST_HOST |
0.0.0.0 |
| Ingest token | MEMPALACE_INGEST_TOKEN |
a long random string |
MEMPALACE_INGEST_TOKEN is optional but strongly recommended in
br0 mode — without it, anyone on the LAN can POST transcripts
into your palace. Generate one with:
openssl rand -hex 32
…and save it; you'll set the same value as MEMPAL_REMOTE_TOKEN on
each client.
Optional
| Name | Key | Default | Notes |
|---|---|---|---|
| Embedding device | MEMPALACE_EMBEDDING_DEVICE |
(auto) | cpu, cuda, dml, or coreml. CUDA needs a rebuild with the [gpu] extra and the NVIDIA driver plugin. |
| Entity languages | MEMPALACE_ENTITY_LANGUAGES |
en |
Comma-separated, e.g. en,es,de. |
Step 8 — Apply and verify
Click Apply. Unraid will start the container; first start takes ~10 s while ChromaDB initializes.
From any machine on your LAN (or from Unraid itself if you enabled Host access to custom networks):
# Unauth liveness check on the ingest port:
curl http://<container-ip>:8766/healthz
# → {"status":"ok","version":"3.3.x"}
# Confirm token gating works (only relevant if you set MEMPALACE_INGEST_TOKEN):
curl -i http://<container-ip>:8766/ingest/transcript
# → HTTP/1.0 401 Unauthorized
For the MCP SSE endpoint:
curl -i http://<container-ip>:8765/sse
# Should hang open with `Content-Type: text/event-stream` —
# that's healthy. Ctrl-C to bail.
If both work, you're done. Container logs are under Docker → MemPalace
→ Logs in the WebUI, or docker logs MemPalace from the shell.
Connect AI tools
Each client needs mcp-proxy:
uv tool install mcp-proxy
# or: pip install mcp-proxy
Claude Code
~/.claude.json (or project .mcp.json):
{
"mcpServers": {
"mempalace": {
"command": "mcp-proxy",
"args": ["http://<container-ip>:8765/sse"]
}
}
}
No bearer header on this path — the MCP endpoint is unauthenticated
in br0 mode.
Codex CLI — ~/.codex/config.toml
[mcp_servers.mempalace]
command = "mcp-proxy"
args = ["http://<container-ip>:8765/sse"]
Antigravity / Windsurf
Settings → AI → MCP Servers, or ~/.antigravity/mcp.json:
{
"mempalace": {
"command": "mcp-proxy",
"args": ["http://<container-ip>:8765/sse"]
}
}
Smoke test
Start a session in any of those clients and ask:
"Use mempalace_status to show palace stats."
You should get back a JSON blob with wing/room/drawer counts. A
connection error means wrong IP or the container isn't on br0.
Set up auto-save hooks (optional)
Enables transcripts to flow into the palace automatically as you work. Each client needs the env vars:
Windows (PowerShell):
[Environment]::SetEnvironmentVariable("MEMPAL_REMOTE_URL", "http://<container-ip>:8766", "User")
[Environment]::SetEnvironmentVariable("MEMPAL_REMOTE_TOKEN", "<the-ingest-token>", "User")
macOS/Linux: same exports in ~/.zshrc / ~/.bashrc.
Then wire hooks/mempal_save_hook_remote.sh and
hooks/mempal_precompact_hook_remote.sh into your client's hook
config — see the "Set up auto-save hooks" section in
README.md, it's identical from this point on.
Note: these hooks talk HTTP, not HTTPS, since this guide doesn't
use Caddy. If MEMPAL_REMOTE_URL starts with http://, no TLS env
vars are needed.
Security notes
br0 puts the container on your LAN with no auth in front of MCP.
That's fine if and only if:
- You trust every device on your LAN (no untrusted IoT, no guest network bridged in, no roommates).
- You've set
MEMPALACE_INGEST_TOKENso nobody can drop transcripts in unprompted.
If those don't both hold, do one of these instead:
- Run on a Tailscale subnet rather than
br0so only your tailnet devices can reach it. - Use the Caddy compose path in
README.mdto add TLS + bearer-token auth on the MCP side too. - Front it with SWAG / Nginx Proxy Manager if you already run
one — drop the bare
8765exposure, route through the proxy with bearer-token auth and SSE pass-through configured.
Troubleshooting
Container starts then dies immediately
99% of the time this is /data ownership. From Unraid shell:
ls -ld /mnt/user/appdata/mempalace
# Want: owner 99 (nobody), group 100 (users)
chown -R 99:100 /mnt/user/appdata/mempalace
Then Docker → MemPalace → Restart.
"Address already in use" on start
Another container on br0 is using the same IP. Pick a different
Fixed IP address in Step 4. (Port conflicts are not possible in
br0 mode since each container has its own IP.)
Unraid shell can't reach the container, but other machines can
Default macvlan behavior. Settings → Docker → Host access to custom networks → Enabled (Step 1).
Clients see SSE connect but tool calls hang
Network path is fine; this is usually a stale mcp-proxy install on
the client. uv tool upgrade mcp-proxy or pip install -U mcp-proxy.
Embedding model download stalls on first request
The ~80 MB MiniLM ONNX model downloads from HuggingFace on first use. Pre-warm it from the Unraid shell:
docker exec MemPalace python -c \
"from chromadb.utils.embedding_functions import ONNXMiniLM_L6_V2; ONNXMiniLM_L6_V2()(['warmup'])"
Subsequent uses load from cache (~50 ms).
Port 8765 / 8766 already used by something else
In br0 mode the container has its own IP, so this only happens if
you literally run a second mempalace on the same IP. Pick a different
fixed IP and restart.
Updating
cd /mnt/user/system/build/mempalace
git pull
docker build -t mempalace-server:latest .
Then in the WebUI: Docker → MemPalace → Force Update (or just
Restart). Your palace data persists because it's on the
/mnt/user/appdata/mempalace volume, outside the container.
Backups
Add /mnt/user/appdata/mempalace/ to CA Backup / Appdata Backup.
Three subdirectories matter:
palace/— ChromaDB vectors + SQLite metadatakg/— knowledge graphinbox/— uploaded transcripts (kept for re-mining)
Losing this directory = losing all memory. The image itself is disposable — you can rebuild it from the repo any time.