main() bound 0.0.0.0 unconditionally. That was defensible while the server was an API for remote agents; now that /ui serves a browser console over the whole vault, a bare `python -m obsidian_wiki.server` on a box with a public address exposes it to the internet. Default to 127.0.0.1 and read WIKI_HOST to override. A container has to listen on every interface, so the Dockerfile now sets WIKI_HOST=0.0.0.0 explicitly — `docker run -p 8080:8080` behaves exactly as before. Closes the loopback-by-default point raised in #208.
3.8 KiB
Deployment — run your vault as a memory service
Everything else in this project is local: skills on your machine, a vault on your disk. This is the one piece that puts a vault behind a URL, so an agent somewhere else — another machine, CI, a hosted product — can search it and write to it.
One container, one vault, one API key. The vault stays plain markdown on a mounted volume, so you can still open it in Obsidian. The container does no LLM work: it searches, reads, writes, and packs. The calling agent does the thinking.
Run it
echo "WIKI_API_KEY=$(openssl rand -hex 24)" > .env
docker compose up --build
The vault lives in the named volume wiki-data. To use a vault you already have, swap the volume
for a bind mount:
volumes:
- /path/to/your/vault:/vault
Locally, without Docker:
pip install 'obsidian-wiki[server]'
WIKI_API_KEY=dev OBSIDIAN_VAULT_PATH=~/vault python -m obsidian_wiki.server
Configuration
| Variable | What it does | Default |
|---|---|---|
OBSIDIAN_VAULT_PATH |
Vault the service serves | /vault |
WIKI_API_KEY |
Bearer token for every /v1/* and /mcp request |
(none — required) |
WIKI_ALLOW_ANONYMOUS |
1 disables auth entirely. Local development only |
(unset) |
WIKI_PORT |
Port to listen on | 8080 |
WIKI_HOST |
Interface to bind. The Docker image sets 0.0.0.0; override only to expose the server deliberately |
127.0.0.1 |
The process refuses to start without WIKI_API_KEY unless WIKI_ALLOW_ANONYMOUS=1. There is no
default key.
Connect an agent (MCP)
claude mcp add --transport http wiki-memory http://localhost:8080/mcp/ \
--header "Authorization: Bearer $WIKI_API_KEY"
Four tools: memory_search, memory_read, memory_write, memory_context_pack.
REST
Every route below /v1 needs Authorization: Bearer <key>. /health does not.
| Method | Path | Notes |
|---|---|---|
GET |
/health |
Liveness; also reports whether the vault directory exists |
GET |
/v1/search?q=&limit= |
Ranked pages with summaries, from the same GraphRAG index the wiki-query skill uses |
GET |
/v1/pages/{path} |
One page as markdown, by vault-relative path |
POST |
/v1/pages |
{title, category, content, tags, sources, summary, upsert} |
POST |
/v1/context-pack |
{topic, budget, recent, public_only, metadata_only} |
curl -X POST localhost:8080/v1/pages -H "Authorization: Bearer $WIKI_API_KEY" \
-H 'content-type: application/json' \
-d '{"title":"Merkle Tree","category":"concepts","summary":"Hash tree for cheap diffing.","content":"Each node hashes its children."}'
# -> {"path":"concepts/merkle-tree.md", ...}
Writes land at <category>/<slug-of-title>.md with the six required frontmatter keys plus summary,
and append a line to log.md. created: is preserved across updates. Use category: "_raw" for a
rough capture you intend to promote with wiki-ingest later — the same role _raw/ plays for the
wiki-capture skill.
POST /v1/pages writes exactly what you send. It does not distil, dedupe, or cross-link — those are
the agent's job, via the wiki-capture and cross-linker skills.
Backups
The vault is a directory, so back it up like one:
docker run --rm -v wiki-data:/vault -v ~/.aws:/root/.aws:ro amazon/aws-cli \
s3 sync /vault s3://your-bucket/vault
Or point the vault at a git remote and use obsidian-wiki sync — see
Configuration.
What this is not
Single-tenant: one container serves one vault behind one key. No per-user isolation, no quotas, no rate limiting, no billing. To serve several people, run a container per vault and put a reverse proxy in front. Terminate TLS at that proxy — the container speaks plain HTTP, and the API key is only as private as the connection carrying it.