Home ยป PostsMy Automated Pipeline for Blogging From ObsidianHere are the details for how I'm using Obsidian, Docker, and CloudFlare to manage this blog.September 5, 2026 - 7 min - Doni SewellTable of ContentsOverviewObsidianThe Build PipelineImagesAnalyticsThe Reverse Proxy and TLSWrapping UpOverviewI set out to make my workflow for posting here as frictionless as possible which means as automated as possible. Anyone who’s spoken to me in a work context for more than five minutes knows my knowledge work revolves around Obsidian so it’s a natural starting point for this workflow.My home lab already makes heavy use of Docker so fitting this all within that architecture also makes this more familiar and maintainable in the long run. I’ve already got backup and maintenance scripts running for the existing containers so adding a few more doesn’t add any more O&M overhead.ObsidianObviously, this all starts with Obsidian. It’s already where all of my writing ends up anyway so it’s a natural place to begin. To keep this even simpler than some other workflows I’ve seen (simple is debatable, as you’ll see) I didn’t even want to add a plugin, of which there are many, to do the export to static site files.I draft locally on my desktop which is automatically synced to my home server using the Obsidian Sync service and Obsidian Headless on the server.To make that safe, there are two gates a note has to pass before it ever reaches the public site.The first gate is location. I keep a single top-level directory in my vault called blog, and that is the only directory the pipeline can even see. Everything else in my vault, all my personal notes, work notes, and the runbooks I keep for the home lab, is invisible to the build process. That is deliberate. I don’t want a stray note or a bad glob to ever have the chance to leak something. Even if tagging is somehow missed or includes too much, it will only ever publish what is inside blog.The second gate is a tag. Even inside that directory, a note is only published if it carries a specific publish tag in its front matter. Drop the tag and the post disappears from the site on the next build. So a post has to be both in the right directory and explicitly marked as ready. Miss either one and nothing happens.To keep the writing experience smooth I use the Templater plugin. I have a template that stamps out the front matter for a new post, the title, the date, a description field, and an empty tag list, so I never have to remember the structure. The publish tag is intentionally left out of the template. I add the tag by hand only when I actually mean it.Front matter is where the post’s metadata lives. Title, date, and description are just standard Hugo keys sitting at the top of the note, and the build passes them straight through. The note is the single source of truth. There is no separate database and no derived state to keep in sync.The Build PipelineHere’s the part that turns a tagged note into a live page. It’s a small custom Docker container running three tools in sequence.The first tool is obsidian-export. Obsidian’s flavor of Markdown is not quite standard, it has wiki-style links, embeds, and callouts that a plain static site generator won’t understand. obsidian-export converts all of that into clean, standard Markdown. It also does the tag filtering for me, so it only exports the notes carrying the #publish tag and ignores the rest.The second tool is Hugo, the static site generator that actually turns the Markdown into the HTML pages you’re reading. Hugo is fast, it’s a single binary, and there’s no runtime, no PHP, no database, just files on disk.The third piece is a file watcher. The container watches the blog directory for changes using inotify, and any time an Obsidian file changes, it kicks off a rebuild. Important note - a single save in Obsidian doesn’t produce one clean “file changed” event, it produces a little burst of them. So instead of rebuilding on every event, the watcher waits a few seconds for the burst to settle and then builds once. It’s a small thing but it keeps the pipeline from firing off a pile of redundant builds while I’m actively editing.The output of all this is a directory of static HTML that gets served by nginx.ImagesI didn’t consider images at first but later realized I might want them.When I paste an image into a note, Obsidian saves the file and drops an embed into the Markdown. obsidian-export faithfully carries that image across, keeping its original filename and its position relative to the note. The problem is a static site generator won’t publish a loose image file that’s just sitting next to a page. It’ll happily build the page and do nothing with the image.The fix is to restructure each post that has images into what Hugo calls a “bundle,” which is really just a directory that contains the post and its images together as a unit. Once the post and its images live in the same directory, Hugo treats the images as part of the page and publishes them right alongside it.I automated that restructuring as a step in the build process. For every post, it reads which images that post actually references, and moves those images into the post’s bundle. Posts with no images need no extra processing. The nice thing is this is completely hands-off. I paste an image, Obsidian syncs, and it just works. The one setting I did change on the Obsidian side was to have new attachments saved into the same directory as the note, so the image and the post stay together from the start.AnalyticsI wanted to know if anyone actually reads any of this, but I did not want to hand my visitors over to a third-party tracker. So I run Umami, a lightweight privacy-respecting analytics tool, and I host it myself.It took a while to figure out how to do this from my own domain, same origin as the blog itself. That means there’s no third-party analytics domain showing up in a visitor’s browser, and the path doesn’t use words like “analytics” or the tool’s name, so the common tracker blockers don’t trip on it. From a visitor’s point of view it’s just another request to my own site.The dashboard where I actually look at the numbers is a different story. That one is locked down to my home network only. No one on the public internet can reach it. The public part of the analytics is minimal and anonymous, and the admin panel isn’t even externally accessible.The Reverse Proxy and TLSSitting in front of all of this is Traefik, which is the reverse proxy that was already running my home lab. It handles TLS certificates automatically through Let’s Encrypt, so HTTPS is a solved problem I never have to think about, and it routes requests to the right service. The blog gets the static site, the analytics paths get routed to Umami, and the private dashboard gets the network restriction.Cloudflare sits out front handling DNS and proxying.Wrapping UpThe end result is exactly what I set out to build. I open Obsidian, I write a note in my blog directory, and when I’m ready to publish, I add one tag. A few seconds later it’s live, with images, with HTTPS, with analytics, and I never touched a CMS or a deploy button. Removing the tag unpublishes it just as quickly.Everything runs on my own hardware, in Docker, alongside the rest of my home lab, using the same backup and maintenance I already had in place. It fits the way I already work.I’ve cleaned up a generic, shareable version of this stack and I plan to put it on GitHub so anyone can stand up the same thing. More on that in a future post.