I have wanted to start a blog for a long time. I tried a few times, even considered creating a YouTube channel, but I always came back to writing.

Over the years, I have come across many small technical blogs where someone documented a very specific problem or solution. I’ve always liked these indie blogs. They may look a little old-fashioned, but that doesn’t make them any less useful.

For notes, ideas, thoughts, and conference talk drafts, I already use Obsidian. I absolutely love this application.

At least for:

  • Convenient tags
  • Cross-links between documents
  • An excellent editor with plugin support, including Vim
  • The note graph (probably Obsidian’s killer feature, which I have never actually used)
  • A file-based database built from plain Markdown files

Ok, wait a second, is that an advantage or a disadvantage?

That depends on the user. It depends on how much important data they have already lost and what they learned from it. In my case, I already run my own server. I know what backups are, and I know why they matter.

For those willing to spend a little time on setup, there is a very useful Obsidian plugin called Self-hosted LiveSync.

In short, the plugin uses a database that can be hosted wherever you want. The same plugin can be installed on all your devices and will synchronize data through that database.

The plugin recommends CouchDB. I deployed the official CouchDB Docker image on my home NAS server and exposed it to the internet through a reverse proxy. After that, all I had to do was enter the database credentials in Obsidian and the plugin settings. Synchronization worked without any issues.

Well, eventually.

(To be honest, it took me a few attempts to get everything working correctly. There are a couple of gotchas.)

I used this setup for about a year. Then one day I had the same thought again:

I had accumulated a number of interesting technical notes, along with one larger piece of material that I used for an internal company presentation. Once again, I started wondering why not make some of this knowledge public. Maybe someone would find it interesting, or even useful.

Making Publishing Boring

This time I decided not to abandon the idea. I did some research, found a few useful tools for running a blog, and realized that a single Markdown file is enough to create a blog post.

And honestly, Markdown already contains everything needed to communicate technical ideas, and non-technical ones too, even with code examples.

For the static site generator, I chose Hugo. Static sites are a great fit for blogs. Thankfully, there is no runtime JavaScript involved.

The basic idea was simple. I would write an article in Markdown, add some metadata, let Hugo generate a static page, and then have my reverse proxy serve the resulting files.

That sounds straightforward enough, but in practice every edit would still require connecting to a server and running commands manually. Knowing myself, I realized that if publishing involved extra steps, I would eventually stop doing it.

The funny thing is that most of the components were already there. I was writing articles in Obsidian, LiveSync was syncing them into CouchDB, and Hugo already knew how to turn Markdown into a website.

At that point, the idea for a small publishing service appeared. All that remained was connecting those pieces together.

I wanted publishing an article to be as boring as possible:

  1. Write the article in Obsidian.
  2. Set post_published: true.
  3. Press Save.

That’s it.

No Git. No manual deployments. No copying files to a server.

But before that could happen, I first had to understand how Obsidian LiveSync stores data inside CouchDB.

Extracting Data from the Database

Let’s start by looking at how data is stored.

I have a document like this:

File metadata

{
  "_id": "daily/2026-01-31.md",
  "children": ["h:li5okkjkz9n7", "h:3v1moid82s2sc"]
}

This document contains note metadata along with references to the chunks that make up the note content.

Chunk document

{
  "_id": "h:li5okkjkz9n7",
  "data": "base64/gzip/..."
}

And this is an example chunk - simply a piece of the document content.

The exact format depends on LiveSync internals. At the moment I decided not to support the E2EE format because I trust the server where my data is stored.

In my case, the data field contains the original text without encryption.

This means the original file can be reconstructed through a fairly simple pipeline.

Something like:

  1. Retrieve file metadata
  2. Read children[]
  3. Batch request all chunks
  4. Concatenate chunks in order
  5. Decode/decompress if necessary

At that point I already had the original Markdown file reconstructed from LiveSync data.

The remaining challenge was figuring out how to react to changes automatically.

Changes Feed

It turns out CouchDB has a feature called a changes feed, and it is perfect for this use case.

Instead of polling the database, you can subscribe to incoming changes and only rebuild the site when a document containing:

post_published: true

is modified.

This is starting to look a lot like an event-driven CMS.

No cron jobs. No periodic checks. No manual publishing.

For the implementation I decided to build a small Node.js service because:

  • CouchDB is JSON-native
  • LiveSync lives in the JavaScript ecosystem
  • Excellent Markdown tooling

Ok, The real reason is that I already know JavaScript.

Service Architecture

The main pipeline looks like this:

Obsidian -> LiveSync -> CouchDB -> Publisher -> Hugo -> Caddy

Hugo does not need to run as a separate service. The publisher container can simply execute hugo whenever a rebuild is required because:

  • Hugo is only needed during the build stage
  • Static websites have no runtime

So the entire setup consists of just a few containers:

  1. CouchDB (already running)
  2. Publisher service
  3. Caddy serving /public

That’s it.

Reverse Proxy

I chose Caddy simply because I like it. Any other web server would work just as well.

blog.example.com {
	root * /srv/blog/public
	file_server
	encode gzip zstd
}

Nothing complicated here.

The Best Part

It is worth mentioning that there is no backend or JavaScript runtime running in production at all.

After the build finishes, only static files remain:

HTML + CSS + JS

Caddy simply serves the contents of the /public directory.

Final Result

The result is exactly the workflow I wanted.

I write articles in the same place where I keep all my other notes. Publishing requires no extra actions beyond saving the file.

It may not be the most conventional blog architecture, but for my homelab it turned out to be surprisingly convenient.

By the way, the article you are reading right now was published using this exact service.


P.S.

If you want to build something similar or simply take a look at the code, here is the project repository:

https://github.com/nikhcevov/obsidian-livesync-extractor


P.P.S.

The latest image has been tested with Obsidian LiveSync version 0.25.74.