← Back to Blog

Caliber, Six Months In: What a $7/Month SaaS Taught Us

Six months of production on a sports-analytics engine running one $7 droplet: 181 days without a reboot, 4,700+ projections graded, and three silent bugs worth studying.

LaravelSeptember 10, 20267 min readBy Joseph Rajewski
Caliber, Six Months In: What a $7/Month SaaS Taught Us

Last spring we wrote about choosing Laravel for Caliber, our sports-analytics SaaS. Making a framework bet in public means you owe a follow-up, so here it is. The twist: production taught us we needed far less architecture than the stack we sketched when we made that decision. No queue workers, no container orchestration, no managed platform. The real story of Caliber's first six months is radical infrastructure minimalism underneath a fairly deep modeling layer, and that inversion is worth a post on its own.

The scoreboard

Caliber went from first commit to first production data in three days at the end of February. Six months later, while the product is still in private beta, the machine has been busy:

  • 4,801 games tracked across 13 sports
  • 4,737 model projections generated, 2,918 of them graded against closing lines
  • 1,180 pick recommendations issued, with roughly 1,200 closing lines archived for calibration
  • 181 consecutive days of uptime, zero reboots since launch
  • Infrastructure cost: $7 a month. One DigitalOcean droplet, 1 vCPU and 1 GB of RAM. Data cost: $0, because every feed we ingest is free and public.

We will be honest about the uptime number: there is no status page or monitoring service behind it, just the server's own uptime counter. But 181 days without a reboot on a box doing per-minute ingestion is not nothing, and the $7 figure is exact, flat, and the stat we are proudest of. Most SaaS budgets have a rounding error bigger than our infrastructure bill.

What held up

Laravel, but a much smaller slice of it than we planned. The original post weighed queues, dashboards, and debugging panels. In production, Caliber runs on five production dependencies and the piece of Laravel nobody writes conference talks about: the scheduler. Fifty-one scheduled entries in Kernel.php do everything a queue cluster would have done for us: a per-minute live-score ingester during game hours, odds pulls three times a day, nightly grading and model recalibration. That is comfortably north of 100,000 scheduled executions since launch, and the scheduler has never been the problem. For an ingest-process-store workload with no user-facing latency constraint, cron plus artisan commands beats a queue you have to operate.

Deploys stayed boring on purpose. No containers, no orchestration, no build pipeline with opinions. Native nginx and PHP, with deploys shipped as a git bundle over SSH. Every ops decision followed the same rule: the fewer moving parts between us and the code, the faster we move when something is wrong.

The ecosystem paid off in small, sharp ways. The best example: when we hit our worst data bug (more below), the entire diagnosis and the verification of the fix happened through artisan tinker over SSH, from a laptop, in one afternoon, with zero debug deploys. And because the whole ingestion, projection, and grading pipeline is plain artisan commands on the scheduler, adding a new nightly calibration job is about ten lines in one file.

What we'd change

We scraped when we should have found the API. Our original team-stats source was scraped HTML from public sports sites. In May of this year, one provider's payload quietly stopped including MLB scoring stats. No error, no empty response, just a field that was no longer there. Every MLB team's rating silently became 0.000, and those zeros fed the model for months before we caught it in early August. The fix was twofold: we replaced the scrape with the official MLB StatsAPI, and, more importantly, we added a data-quality guard that pages us on Discord when any input distribution goes degenerate. The lesson is one every data product learns eventually: scraped sources do not fail loudly, so your pipeline has to.

Summary tables should have shipped on day one. A month after launch we added a daily_model_metrics rollup table and a model_calibrations table for learned parameters, because per-game queries could not answer the question that actually matters: how accurate is the model this season? We knew aggregation would matter and deferred it anyway. If we started today, precomputed accuracy rollups would be in the first migration.

What surprised us

The backend ate the entire complexity budget. We assumed the React frontend would grow into the demanding half of the codebase. The opposite happened: roughly 21,600 lines of backend code against 13,900 of frontend. All the growth went into projection engines, calibration logic, and grading, while the frontend stayed what it should be: a dashboard. If your product's value is a model, expect the model layer to be where the engineering lives.

Zero downtime, three silent data bugs. In six months there have been no user-facing incidents at all. There have, however, been three data-integrity bugs, every one of them silent: the MLB zero-ratings scrape failure (undetected for months), a closing-line-value field that a missing $fillable entry dropped for two months, and a sign error that skewed one calibration. That asymmetry is the honest risk profile of an analytics product. The thing that will hurt you is not the server falling over; it is the server staying up while quietly computing the wrong thing. Our monitoring investment has shifted accordingly: less "is it up," more "is the data sane."

Would we choose it again?

Yes, and with more conviction than the original post had, though for a different reason than we expected. The case for Laravel in that post leaned on its heavy machinery. The case after six months of production is the opposite: Laravel scales down gracefully. A scheduler, artisan commands, five dependencies, and a $7 droplet is a legitimate production architecture for a real modeling product, and the framework never made us pay for the machinery we did not use.

The broader takeaway for anyone evaluating an agency's framework recommendation: ask them to name, in advance, what would prove the choice wrong, and ask for the follow-up once production has had its say. A recommendation that never gets audited is just a preference. We audit ours in public, including the parts where reality disagreed with our sketch. If you want that kind of partner for your next build, get in touch.

#laravel#saas#case-study#production#caliber

Need help with your project?

Let's discuss how Digital Pixel can help bring your vision to life.

Get in Touch