How I Built a Decentralized CMS with Nostr + GitHub Actions

This article walks through how I built a fully decentralized blog using Nostr as the backend CMS, GitHub Actions for automation, and GitHub Pages for free global hosting. No servers, no logins — just sovereign publishing on open protocols.
How I Built a Decentralized CMS with Nostr + GitHub Actions

Andrew G. Stanton - June 8, 2025

How I Built a Decentralized CMS with Nostr + GitHub Actions

🧡 My first post on a fully sovereign blog.

Introduction

This blog post isn’t hosted on Medium, Substack, or WordPress.
It doesn’t live on a database I don’t control.
There’s no login, no admin panel, no SaaS dashboard.

Instead, this post was published as a Nostr event —
stored on dozens of relays,
fetched with open-source scripts,
rendered as a static HTML file,
and served globally via GitHub Pages.

And it all runs without a single server I have to manage.

Let me show you how it works — and why this matters.


Step 1: Writing on Nostr

I use Primal.net to write my articles.
Their long-form editor lets you add titles, summaries, and tags to a special kind of Nostr event (30023).
You just click publish — and the article propagates across the Nostr network.

Step 2: Fetching Events

On my GitHub repo, I maintain a script called fetch_articles.py.
It connects to multiple Nostr relays (like relay.damus.io, relay.primal.net, nostr.mom, etc.) and fetches any articles I’ve written using my public key.
Only posts tagged with blog or article are kept.

Each article is:

  • Deduplicated

  • Parsed for title, summary, image, tags, and content

  • Converted from Markdown to clean HTML

  • Saved in a static folder

Step 3: Hosting with GitHub Pages

The output goes straight into the docs/ folder of my public GitHub repository.
Thanks to GitHub Pages, the site is instantly deployed as a static website.

The homepage (index.html) uses JavaScript to read articles/index.json and display the 10 most recent blog posts — each linking to its full static HTML page.

Step 4: Fully Automated via GitHub Actions

To keep things fresh, I created a GitHub Action called Fetch and Build Articles.
It runs daily — or whenever I trigger it manually — and fetches new content from Nostr, updates the site, and redeploys everything.

No databases.
No plugins.
No backend headaches.
Just content — and the open protocols that carry it.


Why This Matters

Most blogs are brittle.
Lose your password, your server, or your platform — and your voice disappears.

This setup flips that on its head:

  • I own my content — It’s stored on a public, censorship-resistant network.

  • Anyone can verify it — The data lives on Nostr relays, signed with my pubkey.

  • It costs nothing to host — GitHub Pages serves everything statically.

  • It’s future-proof — If GitHub disappeared, I could mirror the repo anywhere.

This is what publishing should look like in 2025.


Want to Build Your Own?

You can fork the repo I use here
or read the code and set it up for your own Nostr pubkey.

If you’re not on Nostr yet, you can find me at:
🧬 npub19wvckp8z58lxs4djuz43pwujka6tthaq77yjd3axttsgppnj0ersgdguvd

This is just the beginning.
One post, one block, one step at a time —
on our own terms.


Write a comment
No comments yet.