AWS basics · Lesson 10 · 40 min

Capstone: costs & clean-up

Almost everyone who learns AWS gets one surprise bill. The usual cause isn't the big server you remember. It's the small things you forgot: an address nobody uses, a spare disk, a load balancer from last month's experiment, a test server in a region you never look at. This capstone gives you an account after a month of practice. Find out what it's costing, hunt down every leftover, and clean it all up with the tools from this path.

You will learn

  • How AWS charges: per hour, per GB-month and per request
  • The usual forgotten-bill suspects, and how to find each one
  • Cost Explorer from the CLI (aws ce get-cost-and-usage)
  • Finding things by tag, and searching every region with a loop
  • Deleting in the right order, because things depend on each other

How the meter runs

ThingChargedRough price (us-east-1)Still charged when…
EC2 instanceper second while runningt3.micro ≈ $0.0104/h ≈ $7.60/monthrunning. Stopped = no compute charge
EBS volume (disk)per GB-monthgp3 ≈ $0.08/GB-monththe instance is stopped, or the volume isn't attached at all
Public IPv4 addressper hour$0.005/h ≈ $3.65/month eachan Elastic IP isn't attached to anything
Load balancerper hour + trafficALB ≈ $16+/monthit has no targets and no visitors
NAT gatewayper hour + per GB≈ $32/month + datanothing uses it
Route 53 hosted zoneper month$0.50it has no records you use
S3per GB-month + requests≈ $0.023/GB-monthold versions pile up in a versioned bucket
CloudWatch Logsper GB stored≈ $0.03/GB-monthretention is "never expire"

Prices change and differ by region, so check the AWS pricing pages (or the AWS Pricing Calculator) before building anything big. Moving data out of AWS to the internet also costs money, after a free allowance.

Where did the money go? Cost Explorer

aws ce get-cost-and-usage \
  --time-period Start=$(date +%Y-%m-01),End=$(date -d tomorrow +%F) \
  --granularity MONTHLY --metrics UnblendedCost \
  --group-by Type=DIMENSION,Key=SERVICE --output table

That's this month so far, per service. "EC2 - Other" is mostly disks. Public IPv4 addresses show up under "Amazon Virtual Private Cloud". Cost Explorer is a little behind real time (hours, not seconds), and in a real account you switch it on once in the billing console.

Hunting leftovers

Tags make this easy, if you used them. One call finds everything with a tag, in one region:

aws resourcegroupstaggingapi get-resources --tag-filters Key=Project,Values=cht \
  --query 'ResourceTagMappingList[].ResourceARN' --output text | tr '\t' '\n'

Most AWS things live in one region, so a server in us-west-2 is invisible while you look at us-east-1. Ask every region with a loop:

for r in $(aws ec2 describe-regions --query 'Regions[].RegionName' --output text); do
  echo "$r: $(aws ec2 describe-instances --region $r \
    --query 'Reservations[].Instances[?State.Name!=`terminated`].InstanceId' --output text)"
done

And the classic forgotten things, each with a filter that finds only the leftovers:

aws ec2 describe-addresses --query 'Addresses[?AssociationId==null]'              # unattached Elastic IPs
aws ec2 describe-volumes --filters Name=status,Values=available                   # disks attached to nothing
aws elbv2 describe-load-balancers --query 'LoadBalancers[].LoadBalancerName'
aws route53 list-hosted-zones --query 'HostedZones[].Name'
aws s3 ls

Deleting in the right order

AWS refuses to delete something another thing still uses, and it tells you with DependencyViolation, ResourceInUse or HostedZoneNotEmpty. Work from the outside in:

  1. Auto Scaling groups (they'd just recreate servers), then instances.
  2. Load balancers, then their target groups (a target group used by a listener can't go first).
  3. Elastic IPs, loose volumes, old snapshots.
  4. S3 buckets: empty first (aws s3 rb --force), including old versions if versioning was on.
  5. Route 53: delete your records, then the zone. (Keep it if you still use the domain!)
  6. Finally the network: security groups (except default), subnets, internet gateway (detach, then delete), route tables, VPC. These are free, but tidy.
The grown-up way: infrastructure as code

You built everything in this path by typing commands, which is great for learning. Real teams describe their infrastructure in files with Terraform/OpenTofu or AWS CloudFormation, and keep them in git. Then "create all of it" and "delete all of it" are one command each, and nothing is forgotten. That's a great next step after this path.

In a real account

Before deleting, be sure it's yours and it's not in use. terminate and rb --force can't be undone. Tags like Project and Owner exist for exactly this moment: nobody wants to guess whether "test-server" is safe to delete.

Practice: stop the money leak 💸

This account has a month of practice left behind: a web server, a load balancer, a spare disk, an unused Elastic IP, a website bucket, a hosted zone… and something in another region. Everything was tagged Project=cht. Find it all and remove it, until nothing is left that costs money.

Quick check

1. You stopped all your instances, but the bill still grows a little every day. What's still charging?

2. aws ec2 describe-instances shows nothing, but Cost Explorer shows EC2 charges. Where do you look?

3. Why can't you delete the target group before the load balancer's listener?

Finished the missions and the quiz? Mark it done to track your progress.

Next up: Security basics · Defend your own server, starting with “Think like an attacker”.