This site isn’t “running” anything. It’s a set of static HTML files sitting on a plain web server. Those files are built from Markdown by Hugo and pushed to the server by a GitHub Actions pipeline, but I never touch either of those directly. My entire workflow lives inside Obsidian. The setup took some effort to configure once, and since then I’ve been able to ignore it completely and just write.

How it’s structured

The Git repository that powers this site is also the Obsidian vault. There’s no separate content directory or export step. Hugo and Obsidian share the same folder tree, with three directories that matter for content:

  • Notes/: posts, mapped to /posts/<slug>/ URLs.
  • Pages/: top-level pages like About, mapped to /<slug>/ URLs.
  • Templates/: Obsidian template files, ignored by Hugo entirely.

Hugo normally expects content inside a content/ directory. Rather than restructure the vault around that, config.toml uses Hugo’s module mount system to remap the folders at build time: Pages/ is mounted to content/, Notes/ to content/posts/, and static/, assets/, and layouts/ to their respective Hugo locations. From Obsidian’s perspective they’re just folders. Hugo never sees the difference.

The theme is a custom one I built for this site, living in themes/notes/. It handles the homepage archive list, single post layout, header, footer, and dark/light toggle.

Writing a post

The community plugin Default Template automatically applies a frontmatter skeleton whenever I create a new file in Notes/:

---
title: 
date: {{date}}
draft: false
tags: []
---

I fill in the title and start writing. Setting draft: true or using a future date keeps something off the live site while I’m still working on it.

Previewing locally

When I want to see how a post looks before it ships, I run a local Hugo server through Docker (docker-compose up), so I don’t need Hugo installed on my machine. It serves the site at http://localhost:1313 with live reload, and it’s configured with --buildDrafts --buildFuture so drafts and future-dated posts show up while I’m working on them. This step is optional. Most of the time I just write and push.

Building and publishing

Publishing starts with committing and pushing, done from within Obsidian using the Git plugin. Once the push lands on the main branch, a GitHub Actions workflow takes over and does the work I’d otherwise do by hand:

  1. Checkout the repository.
  2. Set up Hugo (the extended build, which the theme requires).
  3. Build the static site with hugo --minify, which renders every Markdown file into HTML and writes the result to a public/ directory.
  4. Deploy by uploading the contents of public/ to my web server.

The end result is plain HTML, CSS, and a handful of assets, served directly by the web server with no application runtime, database, or CMS behind it. From pushing in Obsidian to the post being live takes about a minute, and there’s no terminal to open, no build command to run, and nothing to monitor.

Why I like it

Separating the writing tool from the publishing machinery is what makes this comfortable. The build and deploy pipeline is real engineering, but it’s engineering I did once and then stopped thinking about. Day to day, the only tool in front of me is Obsidian. Writing a post and publishing it are the same action, and the static output means the site is fast, cheap to host, and has almost nothing that can break.