Insights Case Study
Case Study

What the Free Tier Actually Costs

Adeolu Timothy 23 July 2026 9 min read Infrastructure · Platform Engineering · Graph Databases · Cost
Summary

Rippli spent a year climbing down the managed-hosting ladder — Replit, then Render's free tier, then a bare VPS — because the workload was the wrong *shape* for free infrastructure, not the wrong size. This is the migration in full: what broke at each rung, what the move actually bought, the security regression we introduced on the way, and how to tell whether your own workload belongs on a platform or on a machine.

#The wrong question

The question teams ask about free hosting is "will we outgrow it?" — as if the ceiling were traffic, and the answer were a date.

Rippli never got close to a traffic ceiling. It ingests live financial news, extracts the companies mentioned, and maps the relationships between them so you can see a supply-chain shock ripple outward. A handful of concurrent users was enough to take the API down.

The constraint was never volume. It was shape. A single request fans out into a couple of dozen article fetches, builds dataframes over them, runs entity extraction and sentiment scoring through a transformer model, and writes into a graph — all inside one synchronous HTTP handler. That shape is memory-bound, stateful and multi-process. Free tiers are priced for work that is I/O-bound, stateless and single-process. No amount of staying small fixes a mismatch like that.

You do not outgrow the free tier. You discover you were never the customer it was designed for.

#What broke, rung by rung

Era Where it ran What broke Actual root cause
Genesis Replit Memory exhaustion running dataframes and a transformer model together; no durable store One environment asked to be an IDE, a runtime and a database
Free cloud Render free + a managed graph tier 502 Bad Gateway on any broad query; 12–25s cold starts; hard node ceiling Heavy synchronous compute inside a 512MB HTTP process
Owned metal Self-hosted VPS, Docker Compose Deployment friction; hand-rolled SSH scripts We bought compute and paid for it in operations

Read that third column downward and the story is not "we needed a bigger box." It is that each environment was solving a different problem than the one we had, and the mismatch surfaced as a different symptom every time.

#The 512MB death trap

Render's free tier gives you 512MB of RAM. A search for something broad — "Apple" — would fetch twenty articles, build DataFrames over them, and load an NLP model into the same process. The web worker hit OOM and died. The user got a 502.

The instructive part is what a 502 teaches you. It carries no information. It does not say memory, it does not name the query that killed it, and it looks identical to a network blip. We spent real time chasing it as an intermittent connectivity issue before accepting that the process was being executed, reliably, by the kernel.

Memory ceilings do not degrade. They terminate. A CPU ceiling makes things slow, which is legible — you see latency climb and you go looking. A memory ceiling produces a healthy process, then no process. There is no gradient to follow, which is why teams misdiagnose it for weeks.

#Cold starts are a product problem wearing an infrastructure costume

Free instances spin down when idle. The first search of the day paid 12 to 25 seconds of cold start before the query began.

The temptation is to file this under performance. It isn't. It lands entirely on first-time visitors and on anyone returning after a gap — precisely the two groups whose opinion of the product is still forming. Your loyal daily user never sees it. Your evaluator sees nothing else. An infrastructure cost that falls only on new users is an acquisition cost, and it should be argued about in those terms.

#The managed-database ceiling arrives before you expect it

Neo4j Aura's free tier caps you at roughly 50,000 nodes and relationships. That sounds generous until you remember what a relationship mapper does: it grows combinatorially. Every new entity is not one row, it is potentially an edge to everything already in the graph. Node-count limits and relationship-count limits are not the same currency, and the second one runs out first.

Underneath that, a slower tax. Every graph query crossed a network boundary from Render to Aura. Individually trivial — a few milliseconds. But a ripple traversal is not one query, it is a fan of them, and per-hop latency multiplies through a traversal in a way it never does through a REST endpoint.

#The thing we got wrong before we got it right

The genuine mistake predates all the hosting decisions: we modelled a graph problem in whatever store was nearest.

Rippli's core object is (Company)-[:SUPPLIES]->(Company). Expressed in SQLite, that is a join table, and every question worth asking — what is three hops from this supplier? — becomes a recursive query that gets slower exactly as the data gets more valuable. Expressed in JSON caches, it becomes application code doing traversal by hand.

We spent months treating that as a performance problem to be tuned. It was a modelling problem, and it stayed unsolved on every platform we tried, because a platform migration cannot fix a data model. The move to Neo4j was the single change that made the rest of the architecture obvious.

If the same bottleneck follows you across three environments, stop migrating. The bottleneck is not the environment.

#What owning the machine actually bought

We rented a bare VPS on a current Ubuntu LTS and moved everything onto it with Docker Compose: the application backend, a relational store, a cache, and a dedicated graph database instance. The managed graph tier was retired entirely. The graph grew freely, its ceiling now set by disk rather than by a pricing page.

Three wins, and only one of them was the compute.

Colocation removed a whole latency class. With the graph database on the same host as the API, graph queries stopped being network calls. The multi-hop traversal that was uncomfortable across a cloud boundary became unremarkable across a loopback interface.

Compose made the orchestration honest. Four services in one file that runs identically on a laptop and on the server. The free-tier architecture had been four vendors, four dashboards and four failure modes that could not be reproduced locally.

The data pipeline became expressible. Once every dependency was one hop away, we could write the pipeline the way we had always described it on a whiteboard — a single context object threaded through an ordered set of stages, each one enriching it and none of them reaching out to the network on their own:

class Pipeline:
    STAGES = (...)                    # each stage: Context -> Context

    def run(self, query: str) -> Context:
        ctx = Context(query=query)
        for stage in self.STAGES:
            ctx = stage(ctx)          # enrich only — no stage fetches
        return ctx

The shape is the transferable part, not the stages inside it. What it buys you is that every stage is independently testable, the whole sequence is reorderable, and moving any one stage to a background worker later becomes a local change rather than a rewrite.

That refactor was not permitted by the VPS in any technical sense. It was permitted because we stopped writing defensive code around four network boundaries, and the shape underneath became visible.

#The part where we made things worse

Owning the machine cost us the one thing Render was genuinely excellent at: push to deploy. In its place we had SSH, git pull, and docker compose up --build, run by hand.

So we automated it badly. Sixty-plus Expect (.exp) scripts, each wrapping an SSH session, several of them with credentials embedded directly in the script.

Every one of those scripts was individually reasonable. Each solved a real annoyance in about five minutes. Nobody ever decided to build a credential-leaking deployment system — it accreted, one small justified file at a time, which is how most security incidents are actually authored.

The fix, when we finally did it properly, was smaller than what it replaced: key-based SSH auth between the local machine and the server, plus two short bash scripts under twenty lines each. Sixty files deleted in a single sweep, and the credentials they had carried rotated on the assumption that anything ever written into a script is already compromised.

The lesson is about the interval, not the fix. The gap between "the platform stopped doing this for us" and "we replaced it deliberately" is where the damage happens. If you take on operations, take on the deployment path in the same week — not after it has been improvised sixty times.

#What changed when the agents arrived

Moving to an agentic IDE changed the maintenance economics more than the writing economics.

The SSH key migration, the sixty-script deletion, the 610MB virtual environment that had been committed, the node_modules prune, the .gitignore standardisation — this is exactly the work that never gets prioritised, because each item is individually too small to schedule and collectively too large to do in an afternoon. Agentic sweeps collapse that category. Work that was uneconomic became routine.

The second-order effect is the one worth internalising. Once agents are deploying to staging and production, AGENTS.md and README.md stop being courtesy documents and become the context an executing system reads. Documentation moved from the lowest-leverage activity on the board to something close to the highest, because it is now load-bearing at runtime.

#How to tell which rung you belong on

The decision is about workload shape. Answer these honestly:

Question If yes If no
Is a request's peak memory predictable and small? Platform is fine You need a machine, or a queue
Does a request finish in under a second of compute? Platform is fine Move the work off the HTTP path first
Is your data model native to the store you're using? Keep going Fix this before migrating anything
Does first-request latency reach an evaluator? Cold starts are an acquisition cost Cold starts are tolerable
Do you have someone who will own patching and backups? A VPS is viable Stay managed and pay

That last row is the one people skip. A VPS is not free — it is unbundled. You take on OS patching, backups, TLS renewal, disk monitoring and intrusion basics. Those costs are real and they are recurring; they simply do not appear on an invoice, which makes them easy to leave out of the comparison and expensive to discover.

#When to skip all of this

Do not read this as an argument against managed platforms. It is an argument against using one for a workload it was not built for. Stay where you are if:

  • Your workload is I/O-bound and stateless — the shape free tiers are actually good at.
  • You are still looking for product-market fit. Managed hosting is a service that buys back your attention, and attention is the scarcest thing you have pre-fit.
  • Nobody on the team wants to own a machine. An unpatched VPS is a worse outcome than a slow API, and it stays worse silently.
  • The bottleneck is a single hot path. Moving that one path to a background worker is far cheaper than migrating everything around it.

That last point is where Rippli is heading anyway. The next milestone severs the heavy NLP work from the HTTP router entirely and moves it onto an asynchronous background worker, so API latency stops depending on query depth. Had we done that early enough, the 512MB ceiling might have held considerably longer — the graph model, however, would still have been wrong.

#Key takeaways

  • Diagnose by workload shape, not size. Memory-bound, stateful, multi-process work does not belong on a free tier at any scale.
  • Treat OOM as its own failure class. It terminates rather than degrades, so you get no gradient to follow and misdiagnose it as flakiness.
  • Fix the data model before you change the platform. A bottleneck that follows you across three environments was never about the environment.
  • Colocate the datastore with the service that traverses it. Per-hop latency multiplies through a graph traversal in a way it never does through a REST call.
  • When you take on operations, take on the deploy path the same week — improvised automation is where the credentials leak.
  • Price a VPS as unbundled, not free. Patching, backups and monitoring are real costs that never show up on the invoice.

Drawn from migrating Rippli — a graph-based financial intelligence platform — from Replit to Render's free tier to self-hosted infrastructure, and from running its news ingestion engine in production alongside it.

Shipping something like this?

Everything above came out of building and running the thing, not researching it. If you are hitting the same problems, I work with product and engineering teams on exactly this — architecture reviews, technical strategy, and staying on through implementation.