How Krabber runs: one box, one table, no framework
// written in 2022: tools, versions and prices may have changed since.
Part of my Krabber series, a Twitter clone in Go. The full source is on GitHub.
Intro
Krabber is my Go port of Crabber, a Python and Flask clone of Twitter. I built my own take on it in Go, kept it under the same open-source license, and credited the original. I’ve spun krabber.net up and down over the years since, mostly because I just enjoy the exercise of running a small, real service end to end.
This post is the whole stack in one pass. The later posts in the series each take one piece and go deep; this one is the map.
I. The shape of it
Here’s the entire thing:
browser
│ HTTPS
▼
CloudFront + WAF static files and avatars cached at the edge
│ HTTPS, CloudFront IPs only
▼
Elastic Beanstalk one t4g.micro: nginx → a Go server
│ server-rendered HTML + htmx
▼
DynamoDB one table: data, sessions, rate limits
CloudFront terminates TLS and runs the WAF rules, and it only lets the origin talk to it, nobody else. Behind it is a single Go server on one Beanstalk box. Behind that is one DynamoDB table that holds everything. That’s the whole production system.
II. One box, on purpose
The server is one t4g.micro instance, not a fleet behind a load balancer. That’s a deliberate cost choice (a load balancer would roughly double the fixed bill), and I wrote up the full Beanstalk-over-Lambda reasoning in its own post. The short version is that a long-running box has a fixed, known cost, and Krabber was already shaped like a long-running server, so it fit.
The background work (fanning new molts out to followers’ feeds, refreshing the public timeline) runs as goroutines inside that same process. A job waiting to run is marked in the table, so if the box restarts mid-job, the next one picks it up. More on that durable-queue trick in the single-table post.
III. No front-end framework
Krabber renders HTML on the server with Go templates and uses htmx for the interactive bits: posting a molt, liking, following, loading the next page. There’s no React, no build step for a single-page app, no client-side router. A click sends a small request, the server renders a fragment of HTML, htmx swaps it in.
I like this more every time I build it. The whole app is one language, the pages work before any JavaScript loads, and there’s no second copy of the rules living in a front-end. For a site this size it’s less code and fewer moving parts, and it’s plenty fast.
IV. One table for everything
All of it (accounts, molts, follows, the home feed, notifications, sessions, and the rate-limit counters) lives in a single DynamoDB table. No Postgres, no Redis, no separate session store. That sounds like a magic trick but it’s really just a few consistent rules about how you shape the keys, which I pull apart in the single-table post. Keeping sessions and rate limits in the same table means one thing to run, back up, and pay for.
V. What it costs, and why that’s a feature
At hobby traffic Krabber runs about $11 a month, and the whole thing is designed so the bill has a known ceiling even under attack. I don’t guess at that number. A meter on the database adds up what every page costs, and a test fails if a page goes over its budget. That’s its own post too, because paying for the thing yourself changes how you build it.
Conclusion
That’s Krabber: one CDN, one box, one table, one language, and a bill I can predict. It started as a Go port of Crabber and became the thing I reach for whenever I want to run something real without a platform team behind me. The rest of this series takes each of these pieces apart, so follow along if the one-box, one-table life appeals to you. Thanks for reading, and welcome to the trench.