Skip to content

How it works

One install serves any number of vaults. Each vault is a folder of markdown files, vaults/<id>/, where the id comes from its name (Work Notes becomes work-notes). The list of vaults is vaults/.registry.json, managed by obsidian-stack.

Two containers serve every vault:

Container Job
obsidian-mcp MCP server, the endpoint clients connect to. One bearer token covers every vault in the install.
obsidian-api REST API over the vault folders. Only obsidian-mcp can reach it. Each request names its vault, and the vault list is re-read on every request, so vaults can be added or removed without a restart.

MCP clients call obsidian_list_vaults to see the vaults, then pass a vault_id to every other tool. With a single vault the id can be left out; the server says which case applies when a client connects.

Each vault stays in sync with other devices on its own, with its own sync source, its own containers, and its own settings and credentials in state/<id>/:

Source Containers per vault On other devices Cost
Self-hosted LiveSync obsidian-<id>-livesync, plus one shared obsidian-couchdb Self-hosted LiveSync plugin, set up from a generated Setup URI Free
Existing LiveSync server obsidian-<id>-livesync An existing LiveSync setup, joined via Setup URI Free
Official Obsidian Sync obsidian-<id>-official-sync (obsidian-headless) Obsidian Sync Subscription
Git obsidian-<id>-git-sync obsidian-git plugin Free
None vaults/<id>/ is managed manually Free

One compose project

The whole install is one Docker Compose project, in the install folder:

File What's in it
docker-compose.yml The core: obsidian-api and obsidian-mcp. Part of the repository.
vaults.compose.yml Every vault's services (its sync, and the Obsidian app if enabled), CouchDB once a vault needs it, and the manager while it's on. Written by obsidian-stack from the vault list, and included by docker-compose.yml.
.env, state/<id>/*.env Secrets and settings, read by the containers when they start. Neither compose file contains them.

Local changes (an extra network, another mount, an environment variable) go in docker-compose.override.yml next to them, which Docker Compose reads automatically and updates never touch. Services from vaults.compose.yml can be extended there by name, e.g. <id>-sync.

So docker compose ps in the install folder lists everything, docker compose logs <id>-sync shows a vault's sync, and docker compose down / up -d stop and start the whole install. Vaults are changed with obsidian-stack (it rewrites vaults.compose.yml), but nothing needs it to run.

All of the install's data is inside the folder, none in Docker volumes:

Folder Holds
vaults/<id>/ The notes
state/<id>/ Each vault's settings, credentials, sync state and LiveSync's local database
state/couchdb/ Self-hosted LiveSync's CouchDB

Moving the install to another server is docker compose down, a copy of the folder, and docker compose up -d on the new one. Devices using self-hosted LiveSync connect by address, so they carry on if the new server keeps the same address; otherwise they need new Setup URIs (obsidian-stack setup-uri <id> --url).

Because credentials are per vault, one install can mix sources and accounts: two vaults on different Obsidian Sync accounts, one on git, another joining a LiveSync server elsewhere, and so on.

Self-hosted LiveSync and HTTPS for phones: Self-hosted LiveSync.