Boring is a compliment.
I started LeafWiki alone, about a year ago. Single Go binary, SQLite, Markdown on disk. No Redis, no Node.js, no Postgres.
Most people building wiki tools today are trying to build the next Notion. Or Confluence. Or Obsidian. I understand the instinct โ those tools are successful, and closing the feature gap feels like progress.
I went the other way.
Three categories I decided not to compete in
Everyone seems to be building a Confluence clone โ team wikis with approval workflows, granular permissions, nested spaces, and enterprise SSO. For a team of five writing runbooks, that’s a lot of surface area to operate.
Everyone seems to be building a Notion clone โ databases, Kanban boards, real-time collaboration, AI summaries. Notion is genuinely impressive. It’s also become everything. People build workarounds inside it for things it was never designed to do. The tool becomes infrastructure. And then the tool needs its own ops team.
Everyone seems to be building an Obsidian clone โ local-first, graph view, personal knowledge management. I like Obsidian. It’s a great tool. But it’s a personal tool. The moment a second person needs to write something, the model breaks.
LeafWiki is none of these. It’s a team wiki. That’s a smaller, more boring problem. That’s the point.
The decision that shaped everything
Markdown files on disk are the source of truth. Not the database.
The database is a cache. On restart, the system reads everything back from the Markdown files. If the database is gone, the content isn’t. Backup means cp -r.
That sounds simple. It isn’t.
Revision history. Link refactoring when pages are renamed. Keeping in-memory state and files consistent across concurrent writes. In a Postgres-backed tool, these are straightforward. When the files are what matters, you do the work properly or you corrupt content.
The easy path is Postgres. I didn’t take it. I wanted content that survives without the application โ readable in a text editor even if the server is gone.
The other thing nobody talks about: atomic writes.
When a user saves a page, two things need to happen: the Markdown file on disk has to be updated, and the in-memory SQLite cache has to reflect the change. You can’t wrap both in a database transaction โ one of them is a file.
The approach: write to a temp file first, then os.Rename(). On Linux, rename is atomic at the filesystem level โ readers either see the old file or the new one, never a half-written state. The SQLite update follows after the rename.
If the process crashes between the rename and the SQLite update, the cache and the files drift. That’s intentional. On restart, the system reads from files and rebuilds the cache. Files win. That’s the contract.
This forces a discipline: nothing important can live only in SQLite. Any feature that stores data only in the cache loses that data on restart. The architecture makes you honest about what the source of truth actually is.
Two more decisions that follow from this:
Page metadata โ IDs, revision references, internal assignments โ lives in the frontmatter of each Markdown file. Not in the database. That way a full reconstruct on restart reads everything it needs directly from the files, with no dependency on cached state that might be stale or missing.
Page ordering within a section is stored in a .order.json sidecar file alongside the Markdown files. Filesystems don’t guarantee consistent ordering across platforms, so you need somewhere to persist it. A tiny JSON file next to the content is the simplest answer that survives a restart, a migration, or a manual cp -r to a new server.
What I said no to
Outline uses Redis for real-time collaboration. In practice, most small teams rarely need two people editing the same page simultaneously โ they assumed they did because Notion has it.
No Redis. No real-time collaboration. No AI features in the UI. No PDF export pipeline that requires Node.js. One binary, no external dependencies.
Every one of these was a reasonable request. Each one would have pulled the project toward something heavier โ something that needs its own maintenance schedule, something that eventually becomes the problem instead of the solution.
What I cared about instead
I like Obsidian’s editor. I wanted that feeling โ sitting down and writing without the interface getting in the way.
Most wikis feel like websites. Click a link, the page reloads, the sidebar resets. Wiki.js had a specific problem I couldn’t ignore: a 404 made the entire navigation tree disappear. A broken link shouldn’t break the interface.
LeafWiki is a SPA. Navigate between pages and nothing reloads. The Go binary delivers Markdown content fast. CSS and JavaScript load once.
Ctrl+Alt+P finds a page by title. Ctrl+V pastes an image. Creating a new page takes seconds.
DokuWiki understood something twenty years ago: files are enough. No database required. The interface just didn’t keep up. I wanted what DokuWiki understood, with an editor you actually want to write in.
Whether it was the right call
About a month ago I noticed the first sponsor on GitHub. Someone from the community โ filing issues, testing versions, shaping features since early on. The custom favicon support came from him. Sections in the navigation tree came from him.
He’d switched from Wiki.js. He’s building a hardware documentation wiki โ parts, assemblies, technical guides for a real project. His description:
“here it’s just ‘create page in sidebar โ upload assets per page โ done’”
He quietly started paying โ without being asked, without a pricing page.
That felt like the right answer to the question.
Pre-1.0, actively developed, free and open source: github.com/perber/leafwiki
Live demo at demo.leafwiki.com โ resets every hour.
v1 is focused on making LeafWiki company-ready โ two-factor authentication, email and user invitations, and Git sync are coming.