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:
- No VPS. Full stop. Nothing to SSH into, nothing to patch at 2 AM because a Go binary decided to allocate memory until it didn’t.
- Lives in my Cloudflare account. Not someone else’s multi-tenant SaaS where my comment data sits in a shared DB. My data, my account.
- Per-comment cost of zero. Or close enough to zero that it rounds down to zero.
- Threaded comments. Flat comment lists are for people who haven’t had a comment thread go sideways and needed to follow the replies.
- Markdown support. Readers on a tech blog want to paste code. Give them code fences or give them chaos.
- Reactions. Low-friction feedback for people who don’t want to write a comment but do want to acknowledge they read the thing.
- OAuth (GitHub + Google) AND anonymous posting. Don’t gate comments behind OAuth. But also don’t leave the door open for bots.
- Turnstile for anonymous spam. Cloudflare’s own CAPTCHA, free up to a million challenges a month, and it doesn’t make users solve puzzles from 1998.
- Embeddable on Astro without wrecking my CSP. My Content-Security-Policy is not ornamental. Whatever I embed needs to play nice with it.
- Admin UI. I’m not running queries against a SQLite database to delete someone’s spam comment. Give me a
/adminpage. - Webhooks out. So I can pipe new comment events to Discord or wherever without polling.
- Email digests. New comments on old posts otherwise disappear into the void.
- RSS feed. For the category of person who wants to follow a post’s comments via their feed reader. Both of them.
- Lazy-loading. Comments are below the fold. The embed script shouldn’t fire until the reader scrolls there — save those Worker invocations.
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
git clone https://github.com/KingPin/Garrul.git commentscd commentsnpm installStep 2: Authenticate wrangler
npx wrangler loginOpens 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
cp wrangler.example.toml wrangler.tomlThen open wrangler.toml and set the non-secret values:
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:
wrangler secret put IP_HASH_SECRETwrangler secret put GH_CLIENT_IDwrangler secret put GH_CLIENT_SECRETwrangler secret put GOOGLE_CLIENT_IDwrangler secret put GOOGLE_CLIENT_SECRETwrangler secret put TURNSTILE_SITE_KEYwrangler secret put TURNSTILE_SECRET_KEYIP_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
npm run migrate -- --remoteThe -- --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
npm run deployTakes 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:
---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:
/* 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:
- You’re not already using Cloudflare. Garrul is Workers-native. Getting it running somewhere else isn’t the design goal and isn’t supported.
- You want a self-hosted single binary with zero vendor lock-in. You want Remark42 — deploy it on whatever box you already have and call it done.
- Your blog is on a VPS you already pay for. You already have the infrastructure. Standing up Remark42 there is the sensible move; running Garrul from a Workers-attached D1 for philosophical purity is just contrarianism.
- You want to evaluate multiple options and pick the best one objectively. I’ve already told you I wrote this, so I’m not the person to ask.
Use Garrul if:
- Your site is on Cloudflare Pages and you want comments without spinning up a VPS.
- You want your comment data in your own Cloudflare account, not in someone else’s managed database.
- You’re already all-in on Workers and the idea of adding one more piece of infrastructure outside that stack is annoying to you on a philosophical level. I understand this demographic very specifically.
- Free tier math works for your traffic volume (it almost certainly does).
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.