A Receipt, not a Brake

October 9, 2026

There is a setting on this budget alarm that would warn me a week earlier, and I am deliberately leaving it off.

It is the only alarm in this entire AWS account. Not a server. Not a certificate. Not a mailbox.

A bill. Fifty dollars a month, everything included, in sixteen lines of Terraform.

And the setting I am refusing is forecast alerting.

AWS Budgets will alert on its projection of where the month is heading, rather than on what you have actually spent. Which sounds strictly better.

On a large account it is. On a fifty dollar budget it is nonsense. Four dollars on the first, one afternoon of experimenting, projects out to a hundred and twenty. The alarm fires. Nothing is wrong.

It fires again next month. And by then it has taught you, correctly, that it does not mean anything.

So on the month something is genuinely running away, you archive it, exactly the way you were trained to.

That is not carelessness. That is the alarm doing it to you.

So this one fires on actual spending, at a hundred percent, to one named person rather than a list. Late, true, and impossible to hand to somebody else.

Because what it watches for is the category of thing that costs money while doing nothing.

A NAT gateway billed by the hour for a client who no longer needs one. A public IPv4 address attached to nothing. A log group with no retention policy, filling up for years.

None of them break anything. None of them page you. Nobody builds a dashboard for things that are working.

And it cannot stop any of it. There is no hard spending cap on AWS, and there never will be, because the switch that kills a runaway experiment is the switch that takes down a company's mail.

It is not a brake. It is a receipt that arrives faster than the invoice, and it is the only thing in this account that would ever tell you.

There Is Exactly One Alarm

Welcome back.

There is exactly one alarm in this AWS account.

Not on a server. Not on a certificate. Not on a mailbox, which is the thing most likely to ruin somebody's week.

The one alarm is on the bill.

Sixteen lines of Terraform. A monthly budget, measured in cost, across every service in the account with no filters applied. Not just the mail, not just the storage, not just the networking. All of it, one number.

The number is fifty dollars.

And attached to it, one notification, with settings that each rule out an obvious alternative. Fire when spending is greater than the threshold. Threshold, one hundred percent. Measured against actual spending. Delivered to one email address.

# Sets up a "general" monthly budget from all sources
resource "aws_budgets_budget" "general_50" {
  name         = "budget-monthly-50"
  budget_type  = "COST"
  limit_amount = "50"
  limit_unit   = "USD"
  time_unit    = "MONTHLY"

  notification {
    comparison_operator        = "GREATER_THAN"
    threshold                  = 100
    threshold_type             = "PERCENTAGE"
    notification_type          = "ACTUAL"
    subscriber_email_addresses = ["sam@madeof.glass"]
  }
}

Four decisions, and the argument for each of them is better than the alarm itself.

Take the Third One First

Take the third one first, because it is the one most people get wrong.

AWS Budgets will happily alert you on a forecast. Its own projection of where the month is heading, based on what you have spent so far.

Which sounds strictly better. You get warned before the money is gone rather than after. Same alarm, more warning, no extra cost.

On a large account with steady spend, that is true. On an account this size it is unusable.

Because a forecast is an extrapolation, and extrapolating from three days of spending on a fifty dollar budget is nonsense.

Four dollars on the first of the month, which is one afternoon of experimenting, projects out to something like a hundred and twenty. The alarm fires. Nothing is wrong.

It fires again next month. And the month after. AWS is not even doing anything unreasonable. It needs roughly five weeks of history before it will forecast at all, and once it has that, it is still fitting a line to a series that is mostly noise at this scale.

The Alarm Doing It to You

And by the third false alarm it has taught you, correctly, that this alert does not mean anything.

So on the month something is genuinely running away, you glance at the subject line and archive it, exactly the way you have been trained to.

That is not carelessness. That is the alarm doing it to you.

An alert that is wrong most of the time is worse than no alert at all, because it costs the same attention and it spends your willingness to look at the next one. You are not just wasting a notification. You are training a person.

So this budget measures actual spending. Which makes it a lagging indicator. By the time it arrives, the money is already gone, and that is the accepted trade.

A late alert that is always true beats an early alert that is usually false.

Actual as of the Last Refresh

It is worth being precise about how late, actually, because billing data is not live and people assume it is.

AWS evaluates budgets roughly three times a day. Cost data lands in the console some hours after the usage happened, and the month does not truly close until several days after the month ends, when the final invoice is cut.

So even the word actual means something like "actual as of the last refresh." Which is fine, as long as nobody builds a process that assumes the number on the screen is the number right now.

One Hundred Percent

Now the threshold. One hundred percent.

The reflex is to warn early. Eighty percent, so there is room to react.

But eighty percent of fifty dollars is forty dollars, and forty dollars is what a perfectly normal month looks like here. There is storage, a content delivery network serving a handful of pages, two corporate directories, the mail, a small instance or two. That is not free.

An eighty percent threshold on this budget fires most months for no reason at all, which is the forecast problem arriving by a different road.

A hundred percent means something a person can act on: this month cost more than a month here is supposed to cost. That is a sentence worth putting in somebody's inbox.

And AWS lets you attach several thresholds to one budget, which is the temptation to resist.

Fifty, eighty, a hundred, a hundred and twenty. It feels thorough. What it actually produces is four emails describing one situation, three of which arrive while nothing is wrong.

One threshold, chosen so that crossing it is genuinely surprising, is the whole design.

One Subscriber

Then the last setting, which is the one people skip entirely. There is one subscriber.

One address. One person. Not a distribution list, not a shared channel, not a ticket queue.

Which is the opposite of what most teams do, because a list feels more responsible. It is not. A message sent to five people is a message four of them assume somebody else is handling, and the fifth one is on holiday.

One name on the alert means one person who cannot pretend they did not see it.

That scales worse than a list, obviously. The day this becomes a team, it becomes a channel with somebody on rotation. But it should become that deliberately, not because a distribution list felt safer on the day it was written.

What It Actually Catches

So what does this alarm actually catch? Because it is worth being precise about that too.

It does not catch growth. If this business slowly grows into a hundred dollars a month of legitimate infrastructure, the alarm fires, somebody looks, and the number gets raised. That is a conversation, not an incident.

What it catches is the category of thing that costs money while doing nothing.

The Honest Catalogue

And that category is bigger than people expect, so here is the honest catalogue.

A NAT Gateway

A NAT gateway is about thirty dollars a month before it moves a single byte, and there is one per availability zone if you followed the reference architecture. Leave one behind for a client who no longer needs private outbound access and it bills by the hour, forever, quietly.

A Public IPv4 Address

Every public IPv4 address now carries an hourly charge, attached or not. Which means an address reserved for a machine that was deleted is a few dollars a month for nothing at all.

Storage That Outlives the Work

An EBS volume outlives the instance it was attached to unless somebody told it not to. Snapshots accumulate on a schedule and nothing deletes them. A log group with no retention policy keeps every line forever, and storage for logs nobody has read since the week they were written is one of the purest forms of waste in cloud computing.

Data Transfer Out

And data transfer out is the classic. It is metered per gigabyte, it does not appear on any resource you can look at, and it is the line item people discover rather than plan.

Every one of those is invisible in the way that matters. None of them break anything. They do not page you, they do not produce errors, and they do not show up in any dashboard, because nobody builds a dashboard for things that are working correctly.

They show up on a bill, thirty days later, and the only reason anybody notices is if something is watching the bill.

Sixteen lines is a very cheap price for the only thing in this account that would ever tell you.

This Alarm Has No Hands

It is also worth being clear about what it cannot do, because budgets get oversold.

This alarm has no hands. It sends a message. That is the entire mechanism.

There is no hard spending cap on AWS. You cannot tell the account to stop at fifty dollars, and no amount of searching the console will find the setting, because it does not exist and it is not going to.

Which is defensible when you think about who else is on the platform. The switch that halts a runaway experiment in a hobby account is the same switch that would take a hospital's systems offline over a billing dispute. Given those two failure modes, the provider picked the one that does not end in a lawsuit.

There Is a Middle Option

There is a middle option, and it deserves an honest mention rather than being left out to make the point cleaner.

AWS Budgets can take actions. Crossing a threshold can apply a restrictive IAM policy, or stop EC2 and RDS instances, automatically.

That is genuinely useful in a sandbox account where a student or a contractor is allowed to experiment and nothing there matters after Friday.

It is completely wrong here. The instances in this account are the mail server and the machines serving client websites. An automated response that stops them turns a forty dollar surprise into a business outage, and it would do it at whatever hour the threshold happened to be crossed. The blast radius of the remedy is larger than the blast radius of the problem.

Notification, Not Control

So what you are buying is notification, not control. The money is already spent. The alarm's only job is to make sure the same money does not go again next month.

Which is worth knowing before you rely on it, because "we have a budget alert" gets said in a tone that implies a brake, and it is not a brake. It is a receipt that arrives faster than the invoice.

An Architecture with a Low Ceiling

And if notification is all you get, then the real cost control has to happen somewhere else. It happens at design time.

This account is fixed-size. Nothing here can respond to traffic by spending more money.

There is no auto scaling group that can decide to be forty machines, no serverless function that can be invoked a hundred million times because of a loop somebody wrote on a Friday, no database that grows storage on demand.

Which means the maximum this account can cost is roughly knowable in advance, and the budget is checking an assumption rather than guarding a gate.

An architecture with a low ceiling is worth more than any alerting you can bolt onto an architecture without one.

Two More Things

Two more things belong in the general practice here, and both are cheaper to do before you need them than after.

Cost Allocation Tags

The first is cost allocation tags. AWS will break your bill down by any tag you like, per client, per environment, per project, but only after you go into the billing console and activate that tag by name. And activation applies going forward. The months before you switched it on stay unlabelled.

Which means the answer to "what does this client actually cost us?" is available to whoever turned tags on a year ago, and unavailable to everybody else. It is a decision that only pays out in the past.

Cost Anomaly Detection

The second is Cost Anomaly Detection, which is the tool that actually solves the problem forecast alerting was reaching for.

It watches your own spending patterns and flags a change in shape rather than a crossing of a line. A service that has never cost anything suddenly costing eight dollars is interesting even though the total is nowhere near the budget. And it is free.

Which is a better fit for a small account than a forecast, because it compares you against your own history rather than extrapolating a trend from four days of noise.

The budget answers "is the total too high." Anomaly detection answers "did something change." Those are different questions, and the second one is the one that catches a mistake on the day it is made rather than at the end of the month.

Where This Thing Lives

Now, where this thing lives in the repository, which is a smaller point that says something about the shape of the system.

It is its own Terraform module, in its own folder, wired into the root of the project alongside identity, networking, and mail.

# Sets up billing alarms
module "billing" {
  source = "./modules/billing"
}

For sixteen lines, that is arguably overbuilt. It could have been four lines in the root file and nobody would have blinked.

But putting it in a module makes a claim about the shape of the system. It says that watching what this account costs is a standing concern at the same level as running the mail, rather than a one-off resource somebody dropped in.

And it means the second budget, a per-service one, or a stricter one for a client's isolated pipeline, has an obvious place to go.

The First Thing That Observes

It is also the first thing in this entire project that observes rather than builds.

Everything before it creates something. A network, a certificate, a mailbox, a machine. This one creates nothing and watches the result of all of it.

Which is a category the codebase did not previously have, and once a category exists, things join it.

Arrival Order

The same pass reorders the root file, and the new order is worth reading top to bottom.

Account-level things first. Permissions, then the budget. Then the shared networking. Then the services that operate across every domain, sending and mail. Then the individual sites.

# Sets up superadmin user account and other general account IAM-related stuff
module "iam" {
  source = "./modules/iam"
}

# Sets up billing alarms
module "billing" {
  source = "./modules/billing"
}

# Sets up account-wide VPCs; keep this empty usually
module "vpc" {
  source = "./modules/vpc"
}

# Sets up SES sending for hosted domains
module "ses" {
  source = "./modules/ses"

  # exclude specific
  ses_zones = { for k, v in var.route53_zones: k => v if ! contains([], k)}
}

# Sends up WorkMail for hosted domains
module "workmail" {
  source = "./modules/workmail"

  # exclude specific
  workmail_zones = { for k, v in var.route53_zones: k => v if ! contains([], k)}
}

# Route53 for the homelab
module "homelab" {

  source = "./modules/homelab"
}

# Serves single-page websites
module "s3_public_html" {
  source = "./modules/s3_public_html"

It goes from things true of the whole account down to things true of one page, which is a real improvement for whoever opens this file first, because this file is the table of contents for everything. Until now it was in arrival order.

And every block gets one line of comment above it. Which sounds redundant, since the blocks already have names. But the names are nouns and the comments are verbs. The block is called identity. The comment says what it does. That is a different piece of information.

So, the Budget

So, the budget.

Fifty dollars, actual not forecast, one threshold, one human. Settings chosen so that the alarm stays believable, which is the only property an alert has that actually matters. Sitting in its own module because watching the money is a permanent job, next to a table of contents that now reads in an order somebody chose.

It will not stop anything. It cannot. What it does is make sure that the first time this account costs more than it should, a specific person finds out in days rather than at the end of a quarter.

Sixteen lines, one number, and one person's inbox. It is the cheapest thing in this repository, and it is the only part of it that is watching.

valar morghulis