Cost Optimization & Billing
AWS bills for what you use, but that doesn't mean costs are automatic. Learn to understand your bill, set guardrails, and reduce spend without reducing capability.
Introduction
AWS's pay-per-use model means costs can grow silently as your system scales. A forgotten NAT Gateway, an over-provisioned RDS instance, or data transfer charges you didn't anticipate can turn a $50/month bill into $500 overnight. Cost optimization is not about being cheap, it is about running the same workload more efficiently so your budget goes further.
1. EC2 Pricing Models
On-Demand
Short-lived or variable workloads
Pay by the hour with no commitments. Highest unit price but maximum flexibility. Use for unpredictable workloads, dev/test, and anything you can turn off.
Reserved Instances
Stable, predictable baseline load (e.g., always-on RDS, base EC2 fleet)
1 or 3-year commitment in exchange for up to 72% discount. Standard RI: locked to instance type and AZ. Convertible RI: can change instance type.
Savings Plans
Steady workloads that may shift between services or instance types
Commit to a $/hour spend (not a specific instance). Compute Savings Plans apply across EC2, Lambda, and Fargate. More flexible than RIs with similar discounts.
Spot Instances
Interruptible batch processing, parallel stateless workloads
Unused EC2 capacity available at up to 90% discount. AWS can reclaim with 2 minutes notice. Ideal for fault-tolerant batch jobs, ML training, and rendering.
Practical strategy: run your baseline steady load on Savings Plans or Reserved Instances. Handle traffic spikes with On-Demand. Run batch jobs on Spot. A typical production account uses all four models for different parts of the same workload.
2. Cost Explorer
Cost Explorer is the AWS console tool for visualizing and analyzing spend. Use it to:
- See cost trends over time (daily, monthly, yearly)
- Break costs down by service, region, account, tag, or resource
- Get Savings Plans purchase recommendations based on your usage history
- Forecast next month's spend based on current usage
# Query last month costs grouped by service aws ce get-cost-and-usage \ --time-period Start=2026-05-01,End=2026-06-01 \ --granularity MONTHLY \ --metrics "BlendedCost" \ --group-by Type=DIMENSION,Key=SERVICE \ --query 'ResultsByTime[0].Groups[?Metrics.BlendedCost.Amount > `10`]' # Find the top 5 most expensive services this month aws ce get-cost-and-usage \ --time-period Start=2026-05-01,End=2026-06-01 \ --granularity MONTHLY \ --metrics "UnblendedCost" \ --group-by Type=DIMENSION,Key=SERVICE \ --output table
3. AWS Budgets
Budgets send alerts when actual or forecasted costs exceed thresholds you define. Set one up immediately on a new account so unexpected resource creation doesn't surprise you at end of month:
# Create a monthly budget with email alert at 80% and 100%
aws budgets create-budget \
--account-id 123456789012 \
--budget '{
"BudgetName": "MonthlyBudget",
"BudgetLimit": {"Amount": "100", "Unit": "USD"},
"BudgetType": "COST",
"TimeUnit": "MONTHLY"
}' \
--notifications-with-subscribers '[
{
"Notification": {
"NotificationType": "ACTUAL",
"ComparisonOperator": "GREATER_THAN",
"Threshold": 80,
"ThresholdType": "PERCENTAGE"
},
"Subscribers": [{
"SubscriptionType": "EMAIL",
"Address": "you@example.com"
}]
}
]'4. Tagging Strategy
Tags are key-value labels on AWS resources. Enable Cost Allocation Tags in the Billing console and your tags appear as dimensions in Cost Explorer. This lets you see cost by team, project, environment, or any dimension that matters to your organization:
# Tag resources at creation time for cost allocation
aws ec2 run-instances \
--image-id ami-xxx \
--instance-type t3.micro \
--tag-specifications '
ResourceType=instance,Tags=[
{Key=Project, Value=checkout-service},
{Key=Env, Value=prod},
{Key=Owner, Value=backend-team},
{Key=CostCenter, Value=engineering}
]'
# In CDK: use Tags at the stack or app level
import aws_cdk as cdk
app = cdk.App()
stack = MyStack(app, "ProdStack")
cdk.Tags.of(stack).add("Project", "checkout-service")
cdk.Tags.of(stack).add("Env", "prod")
cdk.Tags.of(stack).add("CostCenter", "engineering")Tag enforcement: use AWS Config rules or Service Control Policies (SCPs) in AWS Organizations to prevent untagged resources from being created. Untagged resources show up in Cost Explorer as "no tag" and become impossible to attribute to a team or project.
5. Compute Optimizer and Common Waste
AWS Compute Optimizer analyzes CloudWatch metrics for your EC2 instances, Lambda functions, ECS services, and EBS volumes, then recommends right-sized alternatives. Enable it with a single click and check recommendations monthly.
Idle RDS instances
Stop dev databases at night with Lambda + EventBridge (saves ~65% on dev costs)
Unused NAT Gateways
NAT Gateway charges ~$0.045/hr plus $0.045/GB. Delete ones not in use; consolidate where possible
Old EBS snapshots
Automated snapshots accumulate. Use Data Lifecycle Manager to retain only what you need
Unattached EBS volumes
When an EC2 terminates, its EBS may remain. Audit with: aws ec2 describe-volumes --filters Name=status,Values=available
Data transfer costs
Egress (out to internet) is charged; intra-region between AZs is also charged. Use VPC endpoints to keep S3/DynamoDB traffic free
Oversized Lambda memory
Lambda charges per GB-second. Use AWS Lambda Power Tuning (open-source) to find the optimal memory setting
Key Takeaways
- On-Demand for flexibility; Savings Plans for steady compute; Spot for fault-tolerant batch jobs
- Cost Explorer - visualize spend by service, region, or tag; use it to identify surprises and plan Reserved capacity
- Budgets - set up immediately on any new account; alert at 80% to get warnings before you hit the limit
- Tags - apply Project, Env, Owner, and CostCenter tags to every resource; enable cost allocation tags in Billing
- Compute Optimizer - free service that analyzes your actual usage and recommends right-sized resources
- Common waste - idle RDS instances, unused NAT gateways, unattached EBS volumes, and data transfer charges are the top culprits