Skip to content
Go back

Why I Built Garrul

By SumGuy 12 min read
Why I Built Garrul

Disclosure up front: I wrote Garrul. Everything you’re about to read is one guy explaining why he built a thing, not an objective comparison. Read it as “why I shipped it” rather than “why you should pick it.” With that said — I looked at a lot of the popular options before writing a single line, so I at least know what the field looks like.

Here’s what happened: I kept bumping into the same dead end. The solutions I liked most all needed a Docker host, a reverse proxy, and a backup job. My setup doesn’t have any of that. This site runs on Cloudflare Pages with a Workers backend, and I wasn’t about to spin up a VPS just so people could leave a comment about my tmux config. So I wrote something that lives entirely in my Cloudflare account. This is that story, plus the walkthrough if you want to run it yourself.

The Problem With “Just Use Remark42”

Remark42 is great. If you look at the full self-host vs SaaS roundup, you’ll see I recommend it for most people. It’s well-maintained, featureful, and the Docker story is exactly one Compose file. If you’ve already got a VPS running other containers, adding Remark42 is genuinely 20 minutes.

But here’s the thing: not everyone has that VPS. Some of us went 100% Cloudflare — Pages for hosting, Workers for edge logic, D1 for data — specifically because we didn’t want another box to babysit. And “just spin up a DigitalOcean droplet for comments” is hiring a forklift to move a couch. Technically it works. But you now have a $6/month server that you need to keep patched, a reverse proxy config to maintain, SSL certs to renew, and backups to not forget. All for one comment thread.

That’s not a knock on Remark42. It’s a knock on the idea that “spin up a Docker host” is always the right answer. If you’re already on a VPS, absolutely, go run Remark42 there. The whole point of self-hosting is using what fits your life. For me, a VPS didn’t fit.

What I Actually Wanted

I wrote down the requirements before writing any code. It went something like this:

That list isn’t complicated. But nothing I looked at hit all of it while also running on Cloudflare Workers. So.

The Architecture Bet

Garrul runs on four Cloudflare primitives: Workers, D1, KV, and Turnstile.

Workers handles every HTTP request — the API, the OAuth dance, the embed script, the admin UI, all of it. D1 is the SQLite-backed database where comments, users, and sessions live. KV holds sessions and the occasional cache entry. Turnstile gates anonymous submissions. One Worker per site, not multi-tenant — your data lives in your own D1 database in your own account.

The cost story is genuinely good for small operators. Cloudflare’s free tier includes 100,000 Worker requests per day. D1’s free tier covers 5 million rows read and 100,000 rows written per day. KV’s free tier is 100,000 reads per day. For a blog doing a few thousand page views a day, you’re spending exactly zero dollars. Even if you’re big enough to leave the free tier, you’re talking fractions of a cent per request.

What you’re trading is vendor lock-in. Your data is in D1, which is a Cloudflare-proprietary managed SQLite service. If you ever want off, you export the D1 database (it’s SQLite, so the export is a standard .sqlite file) and figure out the rest. That’s it. There’s no “download your data in a common format” button that drops everything into a nice portable bundle. The comments live in your database, but getting them out of Cloudflare’s infrastructure requires actual work.

The “is D1 the right database” debate is also real. D1 has had some rough patches in its history, it’s SQLite-shaped so certain patterns that work fine on Postgres become awkward, and cold-start latency on the write path can be noticeable. For a comment system on a blog, none of this is a practical problem. But it’s worth naming honestly rather than pretending Cloudflare’s entire infrastructure is a neutral implementation detail. You’re betting on Cloudflare’s stack. That bet has worked out for this site, but it’s still a bet.

Deploying Garrul in 20 Minutes

The Garrul GitHub repo has everything. Here’s the condensed walkthrough.

Prerequisites: A Cloudflare account (free plan is fine), a domain on Cloudflare DNS, and Node.js 22 or newer.

Step 1: Clone and install

Terminal window
git clone https://github.com/KingPin/Garrul.git comments
cd comments
npm install

Step 2: Authenticate wrangler

Terminal window
npx wrangler login

Opens a browser tab, you click Allow, done. One-time OAuth handshake with Cloudflare.

Step 3: Register OAuth apps (optional)

If you want GitHub and/or Google sign-in — which you probably do — register OAuth apps now.

For GitHub: go to github.com/settings/developers → New OAuth App. The callback URL pattern is https://comments.<yourdomain>/api/v1/auth/github/callback.

For Google: create a project in Google Cloud Console, enable the OAuth API, create a client ID, set the same callback pattern with google instead of github.

One real footgun with Google: if this Worker will be accessible to the public (i.e., not just you), Google requires app verification before they let arbitrary users sign in. That review process takes 7–10 business days. Either start it early or plan to launch comments-only with GitHub and add Google later.

Step 4: Create a Turnstile widget

In the Cloudflare dashboard, go to Turnstile → Add site. Enter your blog’s domain — the page that will embed the widget, not the Worker’s domain. Grab the Site Key and Secret Key.

Step 5: Configure wrangler.toml

Terminal window
cp wrangler.example.toml wrangler.toml

Then open wrangler.toml and set the non-secret values:

wrangler.toml (excerpt)
name = "comments"
main = "src/index.ts"
compatibility_date = "2026-04-01"
[vars]
ALLOWED_ORIGINS = "https://yourblog.com"
ADMIN_EMAILS = "you@example.com"
PUBLIC_BASE_URL = "https://comments.yourdomain.com"
OAUTH_CALLBACK_BASE = "https://comments.yourdomain.com"
EDIT_WINDOW_MINUTES = "15"
[[d1_databases]]
binding = "DB"
database_name = "garrul"
database_id = "<your-d1-id>"
[[kv_namespaces]]
binding = "SESSIONS"
id = "<your-kv-id>"

The database_id and kv id come from the Cloudflare dashboard after you create those resources — the wrangler.example.toml has comments explaining where to find them.

Step 6: Push secrets

Secrets go through wrangler, never in the config file:

Terminal window
wrangler secret put IP_HASH_SECRET
wrangler secret put GH_CLIENT_ID
wrangler secret put GH_CLIENT_SECRET
wrangler secret put GOOGLE_CLIENT_ID
wrangler secret put GOOGLE_CLIENT_SECRET
wrangler secret put TURNSTILE_SITE_KEY
wrangler secret put TURNSTILE_SECRET_KEY

IP_HASH_SECRET is a random string you generate yourself — it’s the HMAC pepper for hashing IP addresses. Garrul never stores raw IPs; it stores a salted hash of them. The pepper is yours, so even if the D1 database leaked, the IPs stay unrecoverable without it.

Step 7: Run migrations

Terminal window
npm run migrate -- --remote

The -- --remote part is important. Without it, migrations run against the local Miniflare database only. Your deployed Worker would 500 on the first request.

Step 8: Deploy

Terminal window
npm run deploy

Takes about five seconds. Wrangler compiles the TypeScript, bundles everything, and pushes it to Cloudflare’s edge. When it finishes, hit https://comments.yourdomain.com/embed.js — you should get back the widget JavaScript. If you see a file, you’re done.

If you want the complete unabridged version with every footgun documented, INSTALL.md is the source of truth.

Dropping It Into Astro

This is what sumguy.com uses. Create a shared Comments component so every post layout gets comments with one line:

src/components/Comments.astro
---
interface Props { slug: string; title: string; }
const { slug, title } = Astro.props;
const url = Astro.url.href;
const apiOrigin = "https://comments.yourdomain.com";
---
<section class="garrul">
<h2>Comments</h2>
<div
id="garrul"
data-slug={slug}
data-api={apiOrigin}
data-title={title}
data-url={url}
></div>
<script src={`${apiOrigin}/embed.js`} defer></script>
</section>

Then drop it into your post layout:

---
import Comments from "../components/Comments.astro";
const { entry } = Astro.props;
---
<Comments slug={entry.slug} title={entry.data.title} />

Keep data-slug stable. If you change it later, the existing comment thread gets orphaned — Garrul looks up threads by slug and a new slug starts a blank thread. The Astro content collection entry.slug is a good choice because it doesn’t change when you edit the post’s title.

There’s also a complete integration guide at examples/astro in the repo.

CSP for Cloudflare Pages users. If your site has a public/_headers file with a Content-Security-Policy, you need to add your Worker’s origin to the relevant directives. Mine looks like this:

public/_headers (Cloudflare Pages)
/*
Content-Security-Policy: script-src 'self' https://comments.yourdomain.com; connect-src 'self' https://comments.yourdomain.com; frame-src 'self' https://comments.yourdomain.com;

If you have a particularly strict CSP and you’d rather not allow the Worker origin in your host page’s headers at all, there’s an iframe variant that sidesteps the problem — the Worker serves a self-contained page inside an <iframe> and posts its height back via postMessage. See examples/iframe for the auto-resize listener (it’s about 10 lines).

One more thing: the embed script loads when the <script> tag is parsed. If comments are below the fold — and they usually are — you can lazy-load the embed so the Worker invocation only fires when the reader scrolls down to the comment section. On read-heavy pages with lots of visitors who never scroll past the article, this adds up. The pattern is in examples/lazy-load.

Should You Use This?

Honestly? Maybe. Here’s the actual decision tree.

Don’t use Garrul if:

Use Garrul if:

The honest frame: Remark42 is for most people. Garrul is for the specific subset of people who live entirely on Workers and would rather accept Cloudflare vendor lock-in than maintain one more server. That’s a small group. It’s the group I’m in.

What’s Next

Garrul is actively maintained, open source (Apache 2.0), and works on this site right now. Roadmap-wise there are a few things I’m working toward — better moderation tooling, performance work on the embed widget, and some UX polish on the admin UI — but I’m not going to publish a feature list and then be held to it in the comments.

If you use it, run into issues, or have feature ideas, the GitHub repo is where that conversation happens. If you want to support the project: GitHub Sponsors and Ko-fi both work. It’s genuinely helpful.

And if you have thoughts on this article — well, the comment section below is powered by Garrul, so that’s either a great demonstration or a hostage situation, depending on how the deploy went.


Share this post on:

Send a Webmention

Written about this post on your own site? Send a webmention and it'll show up above once verified.


Previous Post
Sysbox vs gVisor vs Kata
Next Post
Glance vs Homepage vs Dashy: Home Lab Dashboards Compared

Discussion

Powered by Garrul . Sign in with GitHub or Google, or post anonymously.

Related Posts