AWS Builder Center

I Asked an LLM to Build AWS Infrastructure and It Tripled the Estimated Bill: Here's How I Added a Pre-Deploy Cost Check

LLMs love "production-grade" defaults like oversized instances and NAT Gateways that quietly inflate the estimated AWS bill. Here is how I add a pre-deploy cost check to AI-generated CloudFormation, so expensive templates get caught before they ever deploy.

The prompt that looks harmless

Imagine telling an AI infrastructure assistant:
"Set up a scalable web app with a database."
Seconds later, you get a valid CloudFormation template. It passes security checks and would deploy without errors. But look at what it would cost per month, and you may find a NAT Gateway, a Multi-AZ database, and oversized instances. Nobody asked for any of that.
Valid and safe does not mean affordable. That is the gap I wanted to close in my AI-powered AWS Deployment Assistant.

Why LLMs overspend

An LLM optimizes for "looks like a good production setup", not for your budget. It has seen countless tutorials where the architecture is highly available and over-provisioned, so it copies that pattern even when you only need a small demo.
Three common culprits:
  • NAT Gateways: charged per hour plus per GB processed, even when idle.
  • Multi-AZ databases: roughly double the database cost.
  • Oversized instance types: the model picks "large" by default because it feels safe.

The idea: a cost gate before deployment

Instead of finding out on the monthly bill, I add one more layer to the pipeline:
1
Plain English → LLM → CloudFormation → Validation → COST CHECK → Deploy
If the estimated cost is higher than the user's budget, the template never reaches deployment.

Step 1: Parse the template

First, extract every resource and its important properties (type, instance size, storage, Multi-AZ flag).
1
2
3
4
5
6
7
8
9
10
11
12
import json

def extract_resources(template_text):
template = json.loads(template_text)
resources = []
for name, res in template.get("Resources", {}).items():
resources.append({
"name": name,
"type": res["Type"],
"props": res.get("Properties", {}),
})
return resources
I convert templates to JSON first. CloudFormation YAML uses short tags like !Ref, which plain YAML parsers do not handle well.

Step 2: Price each resource

Map each resource type to an estimated monthly cost. You can use the AWS Pricing API, or start with a simple lookup table for the services you support.
1
2
3
4
5
6
7
8
9
10
11
HOURS_PER_MONTH = 730

PRICE_PER_HOUR = {
"t3.micro": 0.0104,
"t3.medium": 0.0416,
"m5.large": 0.096,
}

def estimate_ec2(props):
hourly = PRICE_PER_HOUR.get(props.get("InstanceType"), 0.10)
return hourly * HOURS_PER_MONTH
These prices are approximate and vary by region, so always check the current AWS pricing page.

Step 3: Add a budget gate

1
2
3
4
5
6
7
8
9
10
11
def check_budget(total_cost, budget):
if total_cost <= budget:
return {"approved": True}
return {
"approved": False,
"feedback": (
f"Estimated ${total_cost:.0f}/month exceeds the "
f"${budget}/month budget. Reduce cost while keeping "
"the same functionality."
),
}

Step 4: Let the LLM fix its own bill

When the budget check fails, the cost report goes back to the LLM as feedback. The model then replaces the expensive parts: smaller instances, a single-AZ database for dev, and no NAT Gateway where it is not needed.
In practice, one or two rounds of feedback are usually enough.

Before and after (example scenario)

Estimates based on AWS public on-demand pricing (us-east-1, 730 hrs/month). Actual cost varies by region and usage.
ResourceFirst draftAfter cost loop
EC22 x m5.large (~$140)2 x t3.medium (~$61)
Databasedb.m5.large Multi-AZ (~$250)db.t3.medium Single-AZ (~$50)
NAT GatewayYes (~$33 + data)Removed ($0)
Load balancerALB (~$16)ALB (~$16)
Estimated total~$439/month~$127/month
Same functionality for a dev/demo workload, at roughly one third of the estimated cost.

Hidden costs people forget

  • NAT Gateway hourly charge plus data processing
  • Data transfer between Availability Zones and out to the internet
  • Idle load balancers
  • Unattached Elastic IPs and EBS volumes
An estimator that only counts instances will miss a big part of the bill.

Lessons learned

  1. Cost is a guardrail, just like security. Treat it as a hard check, not a suggestion.
  2. Give the LLM the budget upfront. Telling the model the constraint early reduces the number of retries.
  3. An estimate is not a quote. Be honest about approximations and show the user the assumptions.
  4. Feedback loops beat bigger prompts. Letting the model see its own cost report works better than a very long system prompt.

What's next

  • Integrate AWS Cost Explorer to compare estimates with the real bill
  • Add a "free-tier mode" that only allows free-tier eligible resources
  • Show a per-resource cost breakdown in the UI

Final thoughts

AI can write infrastructure faster than any of us. But speed without cost awareness is just a faster way to get a surprise bill. A simple pre-deploy cost check turns an assistant from "it works" into "it works and I can afford it."
If you are building something similar, I would love to hear how you handle cost control. Drop it in the comments!
Any opinions in this article are those of the individual author and may not reflect the opinions of AWS.
Enjoyed reading this content? Let the author know!

Your likes, comments, shares, and saves help creators reach more builders.

Loading recommendations

Loading article