Buy vs. Run: Costing a Rails Production Stack as a Team of One
Every "I saved money by self-hosting" post I've read makes the same mistake: it compares an invoice to an invoice. That comparison is not wrong, it's just not the decision.
I run OddScore — a Rails 8.1 app with a background job tier, full-text search, and a mobile client — on hardware I administer myself. Four web servers, one job server, one database server, all LXC containers on Proxmox. The managed-platform equivalent quotes at roughly six times what I actually spend.
Here is why that gap is the least interesting number in this post.
The line item nobody puts in the spreadsheet
The honest cost of running your own production is:
infrastructure + (ops hours × what your hour is worth) + risk
For me, so far:
- Infrastructure: the server rental, bandwidth, a domain, off-site backup. The smallest term, and the only one anyone ever publishes.
- Ops: under two hours a month of unplanned work. Not "I enjoy tinkering" hours. Hours where something was broken and users could tell.
- Risk: three incidents with user-visible impact, which on a managed platform would have been someone else's pager.
That third line needs a caveat I'll spend most of this series earning. Exactly one of those three was an outage in the sense people mean it — requests failing, under thirty minutes, loud. The other two were silent: search returning empty results for hours, and background jobs quietly not running for most of a day. Both were visible to users. Neither raised a single exception.
Note also that elapsed time is not ops time. A degradation that runs for a day is not a day of work — the day is how long it took me to notice, and the ops hours are what I spent once I did. That distinction is exactly where the self-hosting risk actually lives, and it's why the monitoring posts later in this series exist at all.
Price those ops hours at a senior contracting rate and the arbitrage shrinks — but at two hours a month it does not close. Run the same arithmetic at ten hours a month and it inverts completely. That's the actual model, and most self-hosting advocacy quietly drops the middle term.
Which gives me a number worth watching: my break-even sits somewhere around four hours a month. Below it, self-hosting is cheap. Above it, I'm paying a premium for the privilege of being my own vendor. I know where I am on that line, and that's the whole point of writing it down.
What I was actually optimizing for
Not cost. Control over the failure surface.
On a managed platform, when background jobs stop draining, you open a ticket and wait. That's a fine trade when you have a team and an SLA to point at. As a team of one, my recovery time is bounded by my own understanding of the system — so the constraint I optimized for was: every layer must be one I can read the source of and restart by hand at 23:00.
That constraint produced the architecture. Postgres instead of a proprietary queue. Solid Queue instead of Redis. Minio instead of S3. Prometheus instead of a vendor agent. Each of those is a component whose failure mode I can reason about, at the price of being the person who has to.
The cost savings are a side effect. If I'd optimized for cost first, I'd have made worse choices — cheaper hardware, no standby, no monitoring — and paid for them in the ops-hours term.
What I gave up
Three things, concretely.
Elastic capacity. My web tier is four machines. It is four machines at 3 a.m. and it is four machines during a traffic spike. I sized for a known peak and I know my headroom. Beyond that, I add a container by hand, and "by hand" takes under an hour. A managed platform would have absorbed it before I noticed.
Someone else's on-call. There is no escalation path. The alert routes to Slack and Slack routes to me.
Compliance shortcuts. Every certification, backup attestation, and encryption-at-rest claim is mine to implement and mine to prove. I have not needed to prove any of it yet. The day I do, that's a week of work a vendor would have handed me as a PDF.
The reversibility test
The question I actually ask before any infrastructure decision: if this turns out wrong, how expensive is the reversal?
- Self-hosted Postgres → managed Postgres: a dump, a restore, a connection string. Hours.
- Self-hosted object storage → S3: Minio speaks the S3 API. A bucket sync and an endpoint change. Hours.
- Solid Queue → a hosted queue: schema-coupled, but the job classes don't change. Days.
- Self-hosted Rails on Kamal → a PaaS: the Dockerfile is already the contract. Days.
Every one of those is reversible in days, not quarters. That's the property that made the whole thing a reasonable bet — not the price. I did not build anything that requires a migration project to undo.
The decisions I'd be nervous about are the ones with no exit: a proprietary data format, a framework-level lock-in, a vendor with my only copy of something. I don't have any of those, and that was deliberate.
When buying is obviously right
I'll say this plainly, because the self-hosting genre rarely does.
Buy the managed thing when:
- You have a team. Coordination cost across people exceeds the infrastructure cost almost immediately.
- Your product's differentiation is anywhere near the layer you're considering running. If you're building a search product, do not run your own search cluster to save money. Run it because search is the product.
- The failure mode is regulatory rather than technical. Compliance is bought, not built, unless building it is your business.
- You cannot articulate what breaks first under load. If you don't know your system's first bottleneck, you're not ready to own its recovery.
I fail none of those tests today. If OddScore grows a second engineer, the calculus on at least half of this stack changes the same week.
What I'd have done better
I built this comparison before I had any ops data, so for the first months the middle term was a guess dressed up as a number.
What I should have done from day one is trivial: a text file with one line per unplanned intervention — date, minutes, cause. Instead I reconstructed it afterwards from git history and memory, and memory is systematically kind to the person doing the remembering. A break-even figure is only worth quoting if the hours behind it were measured. Mine are partly remembered, and I'd rather say so than present three significant figures I can't defend.
The other miss: I never separated one-time learning time from recurring ops time. They're different costs with different futures — one amortizes to nothing, the other doesn't — and lumping them together flatters the first month while slandering the steady state.
The artifact
Here's mine, with every figure expressed as a multiple of my actual monthly infrastructure spend. One unit = what I pay for the whole thing.
| Line | Self-hosted | Managed |
|---|---|---|
| Compute (4 web + 1 job) | — | 2.5× |
| Database | — | 1.0× |
| Object storage + egress | — | 0.3× |
| Search | — | 1.2× |
| Observability | — | 1.0× |
| Backup (off-site) | — | included |
| Infrastructure subtotal | 1.0× | 6.0× |
| Ops hours × rate | ~2.5× | ~0 |
| Effective total | ~3.5× | ~6.0× |
| Reversal cost, worst line | days | days to weeks |
Two things fall out of that table that I didn't expect when I started filling it in.
The self-hosted column doesn't itemize. Every line is a dash, because it's one machine. I can't tell you what search costs me, because search costs me nothing marginal — it's a container on a box I'd be renting anyway. That's the saving, and it's also the hidden risk: I have no per-component cost signal. A managed bill tells you every month which part of your architecture is expensive. Mine tells me nothing, so an oversized component can sit there for a year without ever producing a number that makes me look at it.
The gap is 6× on infrastructure and roughly 1.7× once time is priced in. That's still a win. It is a much less impressive win than the headline, and it's the honest version. Anyone quoting you a 6× saving is showing you the top half of this table.
The tradeoff: I traded elasticity and someone else's on-call for a failure surface I can fully reason about — and the savings are a consequence of that choice, not the reason for it.
Comments
Leave a comment
Your comment will appear after moderation.
Your name and email are only used to identify your comment. Your email is never shown publicly. Privacy policy.
Related articles
Github-actions
Run rails tests with github actions matrix strategy
You can easily speedup tests runs by using matrix strategy with github actions. Create a script called for example ci_tests which generates…
Read the article →
Homelab
My Automated Tech News Digest with Claude Cowork Schedules
There are tasks we keep pushing back. Not because they're hard, but because they're repetitive and time-consuming. Daily tech news is one…
Read the article →
Homelab
Build a daily tech digest with n8n, RSS and OpenAI
What you'll build: an automated workflow that aggregates 16 RSS sources every morning (Reddit, Hacker News, newsletters, FR/EN blogs),…
Read the article →