For weeks, arcker.org was a placeholder. The DNS pointed at a VPS, the VPS had Cloudflare in front, and behind Cloudflare a static page said “coming soon.”

I’d been building lithair for over a year. Lithair’s pitch is that one binary can host your sites — TLS, hostname routing, static content, no proxy required. And here I was, hosting my own blog with a proxy in front.

There’s a name for the gap between what you build and what you actually use. Dogfooding. I hadn’t been doing it.

The standard pattern

When you’re shipping a personal blog, the typical stack is some flavor of static site generator (Astro, Hugo, Zola) deployed to a CDN (Netlify, Vercel, Cloudflare Pages). The CDN handles HTTPS, scaling, edge cache. Your job stops at git push. It’s a clean separation, and it works.

I’d had that setup running for arcker.org for weeks before the blog ever served real content. It was solid. There was no operational reason to change it.

But there was a credibility reason.

The move

Lithair already terminated TLS via rustls. Hostname routing was the missing piece — the framework couldn’t tell arcker.org requests apart from lithair.net requests on the same port. So I added it.

LithairServer::new()
    .with_vhost("arcker.org", |v| v.with_frontend_at("/", "sites/arcker.org"))
    .with_vhost("lithair.net", |v| v.with_frontend_at("/", "sites/lithair.net"))
    .with_default_vhost(|v| v.with_frontend_at("/", "sites/lithair.net"))
    .serve()
    .await

Two static sites, one process. The Astro build for the blog now writes into sites/arcker.org/. The lithair showcase writes into sites/lithair.net/. Lithair serves both directly, terminates TLS for both, on the same port.

There’s no CDN in the path anymore. Just my code, on my VPS.

The first night didn’t go smoothly

I made the change in the evening. The vhost routing matched as expected. arcker.org served its placeholder. lithair.net kept serving its showcase. Then I tried to log into the admin UI and got a 404.

The admin UI was registered as a host-agnostic frontend at the top level, and the new vhost matching was exclusive: once a vhost matched, the host-agnostic mounts were skipped. I’d shipped a regression on the same evening I shipped the feature.

A few hours of debugging later, the fix was small: register the admin UI inside each vhost that needs it, not at the top level. The vhost primitive’s exclusivity was correct; my wiring assumed the old behavior.

This is the part of dogfooding people don’t write about. You break your own thing. You feel it. You fix it. The framework is better for it because you used it on something that mattered to you.

Why this matters to me

A framework you don’t use isn’t a framework. It’s a draft.

Every API decision in lithair I’d made for a year had been theoretical until that evening. The vhost primitive’s exclusivity made sense in isolation. It didn’t make sense to me viscerally until it cost me admin access on my own server at 11pm.

If lithair can host my blog, my framework’s showcase site, and my admin UI on a single binary on a single VPS — and survive me being half-asleep when I deploy it — it’s earned the right to be called a framework. That’s the bar.

What this is not

It’s not “you should host your blog yourself.” For most people, a CDN is the right call.

It’s not “Cloudflare wasn’t doing its job.” It was doing it perfectly. I removed it because I needed to use my own thing, not because the alternative was bad.

It’s: when you build something, you should ship it on yourself first.