← The series Episode 5 of 7

$15 a month, itemized by the thing that spends it

Building software got cheap and everybody noticed. Running it was always the expensive half, and that is the half this episode is about. About $9 a month became about $15, every dollar of the increase is a decision I can point to, and the numbers come from the system’s own machine-generated snapshot rather than from my memory.

July 2026

Two lines moved, and I know why

Everyone talks about the cost of building. The cost of running is the one that decides whether a product survives, and it is where the headcount, the on-call rotation and the monitoring bill always went. A product is not what it costs to write. It is what it costs to keep alive, and this episode is that number.

When this project started it ran on about $9 a month. It now runs on about $15 in a typical month and can reach about $40 in a violent one. Nothing drifted. Two lines moved and I know exactly why.

The server went from $5.25 to $7.60, which is $6.99 for the box and $0.60 for its public address. That is the only guaranteed cost in the entire system.

The AI line roughly doubled because the AI took a second job. It used to only write the weather interpretation. It now also runs the system's own operations. When something breaks, a session investigates, writes the fix, and files the postmortem. That work has its own budget, its own key, and its own episode.

Five lines read zero, and they stay at zero because of how the work is arranged rather than because the project is small.

LineTypeWasNow
Hetzner VPSfixed$5.25$7.60
Anthropicusage~$3.60~$7
Neon Postgresfree tier$0$0
Cloudflare R2free tier$0$0
Vercelfree tier$0$0
Grafana Cloudfree tier$0$0
GitHub Actionsfree tier$0$0
Totaltypical~$9~$14.61

Keep the database asleep

Neon bills for the wall-clock time the database is awake, not for how many queries you run, and it suspends after a few minutes of idle. So the whole cost design reduces to one goal that is easy to state and easy to get wrong: keep it asleep.

The pipeline manages that by separating two things that look identical and are not. Checking for change is one activity. Doing work is another. It checks the National Hurricane Center every sixty seconds using signals that never touch the billed database. It only wakes the database when an advisory has actually changed, which happens somewhere between eight and fifteen times a day. The rest of the time the compute is suspended and the meter is stopped.

In the July snapshot, the core database used one compute-hour out of a hundred available.

The monitor that was eating its own budget

The system runs two separate free database projects: a core one for the product and an ops one for health monitoring, so the monitor's heartbeat can never eat the product's allowance. That split was right, and it exposed a second problem. The ops database was burning around 61 of its 100 free compute-hours a month, most of its budget, purely by being woken every minute to record that everything was fine.

The fix was to ask which watcher genuinely needs to be fast. The checks a visitor's experience depends on kept their sixty-second cadence, but they run on Grafana's infrastructure rather than mine, so they cost me nothing per execution. The internal supplemental loop, which watches slow-moving things whose alarm thresholds are measured in hours anyway, dropped to every fifteen minutes.

The ops database now uses under one compute-hour a month. Sixty-one down to less than one, with no loss of coverage, from a single question about which watcher needed to be fast.

The limit is never the one you would guess

Designing for free only works if you can see how close you are to every ceiling, because a free tier that fills up does not warn you. It stops, and the first symptom is an outage. So the system snapshots its own usage from every provider and renders a quota dashboard I can read in ten seconds. That snapshot is also where the numbers in this post come from, which is the arrangement I trust most: the article quotes the system, not the author.

It is not storage. It is not bandwidth. It is not database time. The binding constraint on this entire system is the number of uptime-check executions Grafana's free plan allows, and I use about three quarters of them every month keeping an eye on a hurricane tracker.

Without the dashboard I would have discovered that on the day the checks silently stopped, which is to say on the day the monitoring quietly went dark and I found out from a user. With it, the constraint is a number I watch on purpose.

Free tierUsedLimit%
Grafana synthetic checks73,108100,00073
GitHub Actions minutes4652,00023
Neon ops storage100 MB512 MB20
Vercel invocations78,4501,000,0008
Neon core compute1 CU-hr100 CU-hr1
Cloudflare R2 storage~010 GB0

Five layers of cap

The AI is the only thing here capable of running up a real bill, and it now has two jobs, so the fences grew with it.

The interpretation work keeps its original three layers: a limit on how much a single pipeline pass can spend, a daily ceiling of about a dollar in production measured across the whole day, and a hard account-level limit behind both. No single mistake is enough to produce a surprise. You would have to defeat all three.

The operations work got two more on top. A soft cap per repair session that flags the session for my review when crossed without stopping the work, and a hard fifty-dollar-a-day ceiling that stops everything. It also runs on its own API key, so the repair budget and the product budget are metered separately and neither can quietly drain the other.

That is the same pattern as the two databases. Do not just cap the total. Separate the budgets so a failure in one cannot hide inside the other.

And a way out

Even with all of that, a long-running free project can drift toward a monthly ceiling after a busy season. So there is a deliberate exit: one command provisions a fresh project on a new monthly clock and prints the steps to point the system at it, with no downtime.

It exists for the same reason the caps exist. A plan that depends on a free tier needs a defined move for the day the free tier runs low, and that move is the difference between a free plan that is dependable and one that is hopeful.

What the increase bought

Watching cost this closely used to require a funded team with somebody whose whole job it was. On a side project it would have been unthinkable overhead. It is now cheap enough and visible enough for one person to do properly, and it changes what I can responsibly delegate. I let an AI spend money fixing my system precisely because that spending is capped, metered on its own key, and reported in the same snapshot as everything else.

Roughly two dollars of the increase is the server. The rest is a machine doing operations work that used to interrupt my evenings. I would take that trade at five times the price, and the caps above are the reason I know I will not have to.