A docker-compose development environment that runs a self-hosted Obsidian vault as a shared LLM wiki. CouchDB acts as the durable sync hub (via the LiveSync protocol), a static site viewer provides the web UI, and a one-shot agent container runs Claude Code to perform wiki operations.
┌──────────────────────────────────────────────────────────────┐
│ docker-compose │
│ │
│ ┌──────────────┐ │
│ │ couchdb │ oleduc/docker-obsidian-livesync-couchdb │
│ │ :5984 │ volume: couchdb-data │
│ └──────┬───────┘ │
│ │ │
│ │ LiveSync replication │
│ │ │
│ ┌──────┴───────┐ ┌──────────────┐ │
│ │ agent │────────►│ viewer │ │
│ │ │ shared │ │ │
│ │ livesync-cli│ volume │ Quartz v4 │ │
│ │ claude code │ (r/w) │ :8080 │ │
│ │ │ │ │ │
│ └──────────────┘ └──────────────┘ │
│ │ │ │
│ └─── vault (named volume)┘ │
│ │
│ ┌──────────────┐ │
│ │ sync │ livesync-cli sidecar │
│ │ │ volume: vault (r/w) │
│ └──────────────┘ │
└──────────────────────────────────────────────────────────────┘
External:
Obsidian desktop ──LiveSync plugin──► couchdb:5984
- CouchDB is the single source of truth. It stores vault state in LiveSync's chunk-level document format.
- Obsidian desktop clients connect directly to CouchDB via the LiveSync plugin for real-time sync.
- Sync uses
livesync-clito periodically pull the vault from CouchDB into a local filesystem (vaultvolume). - Agent uses
livesync-clito pull the vault from CouchDB into its own volume (agent-vault), runs Claude Code against it, then pushes changes back to CouchDB. - Viewer mounts
vaultread-only and rebuilds the static site (Quartz v4) when files change.
Image: oleduc/docker-obsidian-livesync-couchdb:master
Pre-configured CouchDB 3.x with LiveSync init script, CORS headers, and
required settings (max_document_size, require_valid_user, etc.) baked in.
- Port:
5984 - Volume:
couchdb-data(persistent) - Healthcheck:
GET /_up
Image: Custom Dockerfile (livesync-cli + Deno)
Periodically pulls vault state from CouchDB to the shared filesystem volume.
- Volume:
vaultat/data(read-write) - Sync interval: configurable (default 60s)
Image: Custom Dockerfile (Quartz v4 + Node 22)
Watches the vault directory for changes and rebuilds the static site. Serves via the Quartz dev server.
- Port:
8080 - Volume:
vaultmounted read-only at/vault
Image: Custom Dockerfile (Node 22 + livesync-cli + Claude Code CLI)
One-shot container. Syncs vault from CouchDB, runs a Claude Code prompt against it, pushes results back. Exits on completion.
- Volume:
agent-vaultat/data(read-write) - Entrypoint:
sync → claude -p "$INSTRUCTION" → push → exit
cp .env.example .env
# Edit .env with your credentials
# Start CouchDB + Portal
docker compose up -d couchdb portal
docker compose run --rm couchdb-init
# Portal is at http://localhost:8000 (login: admin@localhost / admin)
# CouchDB admin UI at http://localhost:5984/_utils/-
Create the vault in the portal (provisions the CouchDB database):
curl -u admin@localhost:admin -X POST \ -H "Content-Type: application/json" \ -d '{"name":"my-wiki"}' \ http://localhost:8000/api/vaults
-
Get the Setup URI (includes CouchDB credentials + encryption passphrase):
curl -u admin@localhost:admin \ http://localhost:8000/api/vaults/my-wiki/setup-uri
This returns
setup_urianduri_passphrase. Save both. -
Create a matching vault in Obsidian (this is a separate step — the Setup URI configures sync but does not create the Obsidian vault):
WARNING: The Setup URI reconfigures LiveSync in whichever vault is currently open. If you apply it in an existing vault (e.g. one already syncing elsewhere), it will overwrite that vault's LiveSync settings. Always create or switch to the intended vault first.
- In Obsidian: File menu → Open another vault → Create new vault
- Name it anything you like (the name is local-only; the CouchDB database name comes from the portal, not from Obsidian)
- Obsidian opens the new empty vault
-
Install and configure LiveSync in the new vault:
- Settings → Community plugins → Browse → install Self-hosted LiveSync
- Enable the plugin, then open its settings
- Choose "Use the copied setup URI"
- Paste the
setup_urivalue, enter theuri_passphrasewhen prompted
-
Choose the setup mode on the decision page:
Scenario Choose First device connecting to a new vault "Setting up for the first time" Joining a vault another device already pushed to "My remote server is already setup" Re-configuring a device that was already syncing "Setup already and compatible" For a brand-new vault, choose "Setting up for the first time". This pushes your (empty) local state to the server and establishes the sync relationship. Future devices joining the same vault use option 2.
-
Start sync + viewer (optional — renders vault content as a web wiki):
docker compose up -d sync viewer # Browse at http://localhost:8080
Each Obsidian vault has independent LiveSync settings (stored in
.obsidian/plugins/obsidian-livesync/). You can sync different vaults to
different CouchDB databases and switch between them freely using Obsidian's
vault switcher. The Setup URI only affects the vault that is open when you
apply it.
Edit portal/users.yaml to add or remove users (changes take effect
immediately, no restart needed):
- email: alice@example.com
password: changeme
groups: [livesync-admin]
- email: bob@example.com
password: changeme
groups: [eng]# Legacy single-vault agent
docker compose run agent -p "initialize the wiki"
# Multi-vault agent (targets a specific vault from the registry)
docker compose run -e VAULT_NAME=my-wiki -e INSTRUCTION="query: what is in this wiki?" agent-multiYou can keep a local directory of markdown files in sync with CouchDB without Obsidian. This is useful for editors, scripts, CI pipelines, or any machine that needs access to vault content as plain files.
docker run -d --restart unless-stopped \
-v ~/wiki:/data \
-e COUCHDB_URI=http://your-couchdb:5984 \
-e COUCHDB_USER=admin \
-e COUCHDB_PASSWORD=changeme \
-e COUCHDB_DATABASE=my-wiki \
-e LIVESYNC_PASSPHRASE=your-e2ee-passphrase \
-e SYNC_INTERVAL=30 \
livesync-syncYour ~/wiki/ directory will contain the vault's markdown files, updated
every 30 seconds. The passphrase is the E2EE passphrase generated when the
vault was created (available via GET /api/vaults/{name}/setup-uri).
Add SYNC_BIDIRECTIONAL=true to also push local file changes back to CouchDB.
Edits you make in ~/wiki/ will appear in Obsidian on other devices:
docker run -d --restart unless-stopped \
-v ~/wiki:/data \
-e COUCHDB_URI=http://your-couchdb:5984 \
-e COUCHDB_USER=admin \
-e COUCHDB_PASSWORD=changeme \
-e COUCHDB_DATABASE=my-wiki \
-e LIVESYNC_PASSPHRASE=your-e2ee-passphrase \
-e SYNC_INTERVAL=30 \
-e SYNC_BIDIRECTIONAL=true \
livesync-syncdocker run -d --restart unless-stopped \
-v ~/vaults:/data/vaults \
-e SYNC_MODE=multi \
-e COUCHDB_URI=http://your-couchdb:5984 \
-e COUCHDB_USER=admin \
-e COUCHDB_PASSWORD=changeme \
-e SYNC_INTERVAL=60 \
-e SYNC_BIDIRECTIONAL=true \
livesync-syncThis syncs all non-encrypted-only vaults to ~/vaults/my-wiki/,
~/vaults/team-notes/, etc. New vaults are picked up automatically.
If you're running the full stack, use the existing services with a bind mount:
# One-way sync of a single vault
docker compose run -v ~/wiki:/data sync
# Bidirectional multi-vault sync
docker compose run -v ~/vaults:/data/vaults \
-e SYNC_BIDIRECTIONAL=true sync-multiThe sync container uses livesync-cli (the headless LiveSync client):
- Bootstrap — on first run, generates a Setup URI and configures
livesync-cliagainst your CouchDB database (same protocol Obsidian uses) - Pull cycle —
sync(CouchDB ↔ local DB replication) thenmirror(local DB → plain .md files on disk) - Push cycle (bidirectional only) —
mirror(detect filesystem changes → local DB) thensync(local DB → CouchDB) - Loop — repeats every
SYNC_INTERVALseconds
The container is stateless beyond the bind-mounted directory. If you delete
the container, the local files remain. Restarting picks up where it left off
(the .livesync/ subdirectory inside the mount stores the bootstrap config
and local DB).
Raw manifests in chart/ target an EKS sandbox cluster:
- CouchDB — StatefulSet with EBS (gp3) persistent volume
- Portal — Deployment with EFS-backed vaults + static volumes, OIDC auth
- Sync — Deployment writing to EFS (multi-vault, registry-driven)
- Agent — Job template with VAULT_NAME targeting
- Ingress — Single ALB with dual listeners: HTTPS/443 (portal, OIDC via Okta) + HTTPS/5984 (CouchDB, no OIDC)
kubectl apply -k chart/See chart/ for details. Assumes EBS CSI and EFS CSI drivers are installed.
The ingress requires an ACM certificate and Okta OIDC app registration
(see chart/ingress.yaml for placeholder values to replace).
For staging or dev clusters, set JWT_SKIP_VERIFY=1 in the portal configmap
and use scripts/fake-jwt.py to generate test tokens:
TOKEN=$(python scripts/fake-jwt.py --email test@co.com --groups livesync-admin | head -2 | tail -1)
curl -H "x-amzn-oidc-data: $TOKEN" https://portal-staging.example.com/api/vaultsSee TODO.md for the detailed implementation checklist.