Own Every Layer

Running production at zero fixed infrastructure cost

Belief 04 · 15 min read ·

“Zero infrastructure cost” usually means one of three things: a free tier about to expire, a credits programme about to run out, or a system that has never seen load. Ours means something more boring and more durable.

Our stated target is no mandatory monthly infrastructure cost at roughly 100 daily active users, in production, without giving up security, data ownership, or portability. Payment-processing fees per transaction are fine — they scale with revenue. A mandatory monthly subscription for required infrastructure is not.

A per-transaction fee is a share of income. A monthly floor is a debt you service whether or not anyone shows up.

The mechanism, not the trick

There is no clever billing arrangement. There is an architectural constraint that made the cheap path and the correct path the same path: every provider category sits behind an interface the core domain owns, and every interface ships a working free default selected through exactly the same configuration mechanism a paid implementation would use.

That last clause matters more than it looks. The free path is not a special-cased “cheap mode” branch. It is the same code path with a different value, which is why it does not rot and why moving off it is uneventful.

Consider the alternative most teams build without meaning to: a development configuration using local everything and a production configuration using managed everything. Those two diverge within weeks, because only one is under real load and only the other is under real scrutiny. Then the day comes when you need to reproduce a production bug locally and you cannot, because the two paths stopped being the same path some time in March.

What that looks like per category

CategoryWhat we useWhat we refused
QueueA Postgres tableKafka, SQS, Redis Streams
SearchPostgres full-text, pgvector if ever justifiedElasticsearch, OpenSearch, a vector database
CacheIn-memory; correct with it cold or absentA managed cache as a source of truth
StorageLocal disk, object storage behind one interfaceVendor storage semantics in the domain
InferenceDeterministic mock by defaultA required API key to run the tests

There is a general lesson underneath the specifics, and it is one of the most useful things an engineer can internalise early: Postgres is several products. It is a relational store, a document store, a job queue, a full-text search engine, a pub-sub bus, and with an extension a vector index. Every one of those is worse than the dedicated tool at the extreme, and better than the dedicated tool below the extreme — because it is one system to operate, one backup to take, and one transaction to reason about.

The ledger

We keep a table of every free tier we depend on, with the allowance, the restrictions, the pricing source, and the date we last verified it. The rule attached to that table is blunt: never treat any tier as free forever, and re-confirm the numbers before shipping a dependency on them.

The verification date is the column that does the work. Free-tier terms change quietly and the announcement rarely reaches you. A dependency you last checked fourteen months ago is not a known quantity. It is a guess with a citation.

One decision worth stating explicitly, because it catches people: we chose our hosting partly because its free tier permits commercial use, having ruled out a more generous one that does not. Capacity is the number everyone compares. The licence is the term that actually decides whether you can ship.

The honest part: what free costs

These are measured, not estimated, and they are genuinely unpleasant.

Free compute sleeps after fifteen minutes idle. When the next request arrives the service starts from cold. We measured both of ours:

Cold start after 15 min idle
  Java service     ~196 s
  Python service    ~22 s

Three minutes is not a rounding error. It is a user watching a spinner and concluding the product is broken, which for a first-time visitor is indistinguishable from the product being broken. We do not describe that as an acceptable tradeoff. We describe it as a known defect with a known cause that we carry while we are pre-revenue. The mitigation is a scheduled request that keeps the service warm, and that is a workaround, not a fix.

The gap between the two numbers is instructive on its own. Both services sleep identically; one takes nine times longer to wake. That is JVM startup plus framework initialisation plus connection-pool warm-up — the price of a choice we explain in the polyglot note. Worth it for the domain, expensive on this axis, and the honest way to describe it is as a trade rather than a win.

The second real constraint: a free Postgres tier with a six-hour point-in-time recovery window. That is not a backup story, it is a partial backup story, and it bounds what we can promise about data loss. We say so rather than describing our recovery posture in the vocabulary of a company paying for a longer window.

The migration path is the product

We keep a planned migration path per category, so a tier tightening or disappearing is a budgeted transition rather than an outage. That plan is the actual product of this work. The zero is a side effect.

Each path answers three questions: which paid implementation we move to, what the configuration change is, and what data has to move. For the queue the answers are short — an adapter, one configuration key, and no historical data because in-flight jobs drain first. For the database they are much longer, which is exactly why it is worth having written them down before the day we need them.

Why the zero is not the point

The number people react to is the cost. The number that matters is the cost of change. When a workload outgrows a free tier we change a configuration value. Teams that treat infrastructure spend as something to negotiate later usually end up negotiating their roadmap instead — because by then the vendor is not a line item, it is a load-bearing wall.

The failure mode is not the bill. It is the meeting where a technically correct decision loses to a migration estimate.

Try this

  1. Price your side project twice on one line: what it costs today, and what it costs if every free tier you rely on ended tomorrow. Most people have never written the second number down.
  2. Open the terms of the tier you depend on most and find the commercial-use clause. Not the capacity table. The clause.
  3. Estimate, in hours, what replacing your most expensive dependency would take. That estimate is the real measure of your independence.

Own every layer.