title: “CouchDB + couch-sync” date: 2026-04-14 tags:

  • couchdb
  • self-hosted

Obsidian + obsidian-livesync · Overview · Next: Quartz v4 →


CouchDB

Apache CouchDB is a document database that speaks HTTP natively. Every operation — reads, writes, changes — is a plain HTTP request. No drivers, no special clients needed.

Install on a VPS (Debian/Ubuntu)

curl -fsSL https://couchdb.apache.org/repo/keys.asc | gpg --dearmor \
  | sudo tee /etc/apt/trusted.gpg.d/couchdb.gpg > /dev/null
 
echo "deb https://apache.jfrog.io/artifactory/couchdb-deb/ $(lsb_release -sc) main" \
  | sudo tee /etc/apt/sources.list.d/couchdb.list
 
sudo apt update && sudo apt install -y couchdb

Choose standalone mode during setup and set a strong admin password. Verify:

curl http://admin:password@127.0.0.1:5984/
# {"couchdb":"Welcome","version":"3.x.x",...}

Create the database

curl -X PUT http://admin:password@127.0.0.1:5984/obsidian

obsidian-and-livesync creates this automatically on first connect, but creating it manually lets you verify CouchDB is healthy first.

The _changes Feed

CouchDB’s key feature for this pipeline is its changes feed — a continuous HTTP stream that delivers a JSON line for every document write:

GET /obsidian/_changes?feed=continuous&include_docs=true&since=now

Each change:

{"seq":"42-...","id":"my-note.md","changes":[{"rev":"3-abc"}],"doc":{...}}

couch-sync subscribes to this — no polling required.


couch-sync

couch-sync is a ~200-line Node.js ESM script that bridges CouchDB and Quartz:

  1. Opens a persistent HTTP connection to _changes?feed=continuous.
  2. Filters for documents whose _id ends in .md.
  3. Writes a markdown file to content/ from the document’s extracted_text field.
  4. Schedules a debounced Quartz rebuild (2-second delay).
  5. Auto-reconnects after 2 seconds if the stream drops.

The Transform Function

function transform(doc) {
  return `---
title: ${doc._id.replace(/\.md$/, "")}
---
 
${doc.extracted_text || ""}
`
}

extracted_text holds the plain markdown body. The title is derived from the document _id.

Caveat: obsidian-livesync encrypts content, so extracted_text is empty unless encryption is disabled or a decryption step populates it.

Visibility Control

if (doc._deleted || doc.published === false) {
  deleteFile(doc._id)
} else {
  const md = transform(doc)
  writeFile(doc._id, md)
}

Set "published": false on a CouchDB document to remove the page from the site on the next change event.

The Debounce

A 2-second debounce means bursts of rapid saves trigger only one Quartz build at the end. For bulk imports, pause couch-sync, insert all documents, then restart.

Sequence Tracking

The last processed change sequence is stored in .seq. On restart, couch-sync resumes from there — no missed or replayed changes. Deleting .seq forces a full replay (safe but slow).

Running in Production

pm2 start sync.js --name couch-sync
pm2 save && pm2 startup

Security

The initial implementation hardcodes admin:admin. For any internet-facing server:

  • Use a strong, unique CouchDB password.
  • Store credentials in environment variables.
  • Put CouchDB behind a reverse proxy with TLS.
  • Restrict CouchDB to 127.0.0.1obsidian-livesync can connect through an SSH tunnel.

Obsidian + obsidian-livesync · Quartz v4 →