6.8 KiB
Self-hosted LiveSync — Docker Setup
A fully self-hosted CouchDB stack for the obsidian-livesync plugin.
No fly.io. No IBM Cloudant. No cloud accounts required for basic use.
✅ Tested on Docker Desktop for Windows (Docker 29.2, Compose v5, WSL2 backend) — full init, CORS, auth, and idempotent restart verified.
Architecture
Obsidian (desktop / iOS / Android)
│ CouchDB Replication Protocol (HTTPS)
▼
[ Reverse Proxy / Tunnel ] ◄── Choose ONE profile below
│
▼
[ CouchDB container ] ◄── The only required service
│ initialized once by couchdb-init container
▼
[ Named Docker Volume ] ◄── All vault data stored here
Quick Start
1. Prerequisites
- Docker Desktop (Windows/Mac/Linux) or Docker Engine + Compose plugin
- A machine that Obsidian devices can reach over HTTPS (see profiles below)
2. Configure
cd docker/
cp .env.example .env
# Edit .env — at minimum set COUCHDB_USER and a strong COUCHDB_PASSWORD
3. Launch
# Default: CouchDB only (LAN / localhost, no TLS)
docker compose up -d
# With Caddy (public domain + auto Let's Encrypt)
docker compose --profile caddy up -d
# With Tailscale (no domain needed, private mesh or public Funnel)
docker compose --profile tailscale up -d
# With Cloudflare Tunnel (Cloudflare account required)
docker compose --profile cloudflare up -d
4. Verify
# Should return {"status":"ok"}
curl -u admin:yourpassword http://localhost:5984/_up
# Check CORS headers
curl -v -H "Origin: app://obsidian.md" \
-u admin:yourpassword \
http://localhost:5984/
5. Connect Obsidian
In the Obsidian plugin settings (Self-hosted LiveSync):
| Field | Value |
|---|---|
| URI | https://your-domain-or-ts-hostname:5984 (or http://localhost:5984 for LAN-only) |
| Username | value of COUCHDB_USER |
| Password | value of COUCHDB_PASSWORD |
| Database name | value of COUCHDB_DATABASE (default: obsidiannotes) |
| End-to-end passphrase | your own chosen passphrase — never stored server-side |
Profile Details
Default (no profile) — LAN / Localhost only
CouchDB is exposed on http://localhost:5984 (or LAN IP).
Desktop Obsidian works over HTTP. Mobile Obsidian requires HTTPS — use a tunnel profile.
--profile caddy — Public Domain + Auto TLS
Requires:
- A domain with an A record pointing to this server's public IP
- Ports 80 and 443 open in your firewall/router
Set in .env:
COUCHDB_DOMAIN=couchdb.yourdomain.com
ACME_EMAIL=you@example.com
Caddy automatically issues a Let's Encrypt certificate. No manual cert management.
--profile tailscale — No Domain Required ✅ Recommended for privacy
Requires:
- Free Tailscale account
- Install the Tailscale app on all your Obsidian devices
- Generate an OAuth key at: https://login.tailscale.com/admin/settings/oauth
(Scopes:devices:write)
Set in .env:
TS_AUTHKEY=tskey-auth-...
TS_HOSTNAME=livesync
Two sub-modes:
- VPN mode (default): CouchDB accessible only to devices on your Tailnet at
https://livesync.<tailnet>.ts.net— completely private - Funnel mode: public HTTPS at
https://livesync.<tailnet>.ts.net— no domain purchase
Enable in your Tailscale ACL:"nodeAttrs": [{"target": ["tag:container"], "attr": ["funnel"]}]
Note on Windows Docker Desktop: If
/dev/net/tunis unavailable, addTS_USERSPACE=true
to the tailscale service environment indocker-compose.yml.
--profile cloudflare — Cloudflare Tunnel
Requires:
- Cloudflare account with a domain on Cloudflare DNS
- Tunnel token from Zero Trust → Networks → Tunnels → Create tunnel
Set in .env:
CF_TUNNEL_TOKEN=eyJhI...
COUCHDB_DOMAIN=couchdb.yourdomain.com
⚠️ Known issue: Cloudflare has a 100s proxy timeout that interrupts CouchDB's
long-polling replication feed, causing524errors.
Fix: In the Obsidian plugin settings enable:
"Use Request API to avoid inevitable CORS problem"
Data & Backup
All vault data lives in the couchdb-data Docker named volume.
# Backup
docker run --rm -v obsidian-livesync_couchdb-data:/data \
-v $(pwd)/backup:/backup alpine \
tar czf /backup/couchdb-backup-$(date +%Y%m%d).tar.gz -C /data .
# Restore
docker run --rm -v obsidian-livesync_couchdb-data:/data \
-v $(pwd)/backup:/backup alpine \
tar xzf /backup/couchdb-backup-20260218.tar.gz -C /data
Security Notes
- CouchDB requires authentication for all requests (configured by
livesync.ini) - Enable End-to-End Encryption passphrase in the Obsidian plugin — vault data is
encrypted before it ever leaves your device - The init container runs once and exits — it has no persistent access
- Never expose CouchDB's admin interface (
/_utils) to the public internet;
use a firewall rule or the path-based obfuscation trick from
self-hosted-livesync-server
Useful Commands
# View logs
docker compose logs -f couchdb
docker compose logs couchdb-init
# Re-run init (e.g. after changing credentials)
docker compose restart couchdb-init
# Stop without removing data
docker compose down
# Stop AND remove all data volumes (DESTRUCTIVE)
docker compose down -v
# Open CouchDB admin UI (Fauxton) in browser
open http://localhost:5984/_utils
Troubleshooting
| Problem | Solution |
|---|---|
| Init container keeps restarting | CouchDB not healthy yet — wait 30s, check docker compose logs couchdb |
curl: (52) Empty reply |
CouchDB not fully started — the healthcheck should gate this |
| Mobile can't connect | Needs HTTPS — use tailscale or caddy profile |
| 524 errors with Cloudflare | Enable "Use Request API" toggle in Obsidian plugin |
Permission denied on volumes |
Run docker compose down -v and retry — first-run volume ownership issue |
| CORS errors in browser | Confirm CouchDB headers: curl -v -H "Origin: app://obsidian.md" http://localhost:5984/ |
| CouchDB exits immediately, zero logs (Windows) | Do not add :ro to the livesync.ini volume mount. CouchDB's entrypoint runs chmod 0644 on all files in /opt/couchdb/etc — read-only bind mounts cause a silent EPERM crash on Docker Desktop for Windows (WSL2). The compose file is already correct; do not modify it. |
Settings in livesync.ini seem ignored |
Settings requiring restart (e.g. bind_address) load at start. Runtime-only settings (require_valid_user, enable_cors) are set by the init container via REST API and take effect immediately without restart. |