All examples for AWS Resource Cleanup Runbook

Hidden Costs Investigation

Audit guide for finding hidden AWS costs including CloudWatch Logs retention, S3 versioning bloat, old Lambda versions, orphaned secrets, unused KMS keys, and ECR untagged images. Potential savings: $50-440/month.

Input

What you provide to the skill

My AWS bill is $500/month but I can’t figure out where it’s all going. What hidden costs should I look for?

Output

What the skill generates for you

Hidden AWS Cost Audit

AWS bills often hide costs in small line items that add up. Here’s what to check:

Often-Overlooked Cost Sources

Hidden Cost Why It’s Missed Typical Cost Difficulty
CloudWatch Logs retention Default is “never delete” $10-100/month Easy
S3 versioning bloat Old versions accumulate $20-200/month Medium
Old Lambda versions Versions persist forever $5-50/month Easy
Orphaned Secrets Manager $0.40/secret/month $5-20/month Easy
Unused KMS keys $1/key/month $5-20/month Easy
ECR untagged images $0.10/GB/month $5-50/month Easy
Data transfer NAT, load balancers $50-500/month Hard

1. CloudWatch Logs with No Retention

Default retention is “never expire” - logs accumulate forever.

Find log groups without retention:

aws logs describe-log-groups \
--query 'logGroups[?retentionInDays==null].[logGroupName,storedBytes]' \
--output table

Set 30-day retention (adjust based on compliance needs):

# For a single log group
aws logs put-retention-policy \
--log-group-name /aws/lambda/my-function \
--retention-in-days 30
# For all log groups without retention
aws logs describe-log-groups \
--query 'logGroups[?retentionInDays==null].logGroupName' \
--output text | while read group; do
echo "Setting retention for $group"
aws logs put-retention-policy \
--log-group-name "$group" \
--retention-in-days 30
done

Typical savings: $10-100/month

2. S3 Versioning Bloat

If versioning is enabled, old versions stay forever.

Check versioning status:

# List all buckets
aws s3api list-buckets --query 'Buckets[*].Name' --output text | while read bucket; do
status=$(aws s3api get-bucket-versioning --bucket $bucket --query 'Status' --output text)
echo "$bucket: $status"
done

Check storage by version (requires S3 Storage Lens - check in console):

  • Go to S3 > Storage Lens > Default dashboard
  • Look for “Current version bytes” vs “Noncurrent version bytes”

Set lifecycle rule to delete old versions:

cat > lifecycle.json <<'EOF'
{
"Rules": [
{
"ID": "Delete old versions after 30 days",
"Status": "Enabled",
"NoncurrentVersionExpiration": {
"NoncurrentDays": 30
}
}
]
}
EOF
aws s3api put-bucket-lifecycle-configuration \
--bucket my-bucket \
--lifecycle-configuration file://lifecycle.json

Typical savings: $20-200/month

3. Old Lambda Versions

Lambda keeps all versions forever. Each version consumes storage.

Find functions with many versions:

aws lambda list-functions \
--query 'Functions[*].FunctionName' \
--output text | while read func; do
count=$(aws lambda list-versions-by-function \
--function-name $func \
--query 'length(Versions)' --output text)
echo "$func: $count versions"
done

Delete old versions (keep $LATEST and last 3):

function_name="my-function"
aws lambda list-versions-by-function \
--function-name $function_name \
--query 'Versions[?Version!=`$LATEST`].Version' \
--output text | tr '\t' '\n' | head -n -3 | while read ver; do
echo "Deleting version $ver"
aws lambda delete-function \
--function-name $function_name \
--qualifier $ver
done

Typical savings: $5-50/month

4. Orphaned Secrets Manager Secrets

Each secret costs $0.40/month. Old secrets add up.

List all secrets:

aws secretsmanager list-secrets \
--query 'SecretList[*].[Name,CreatedDate,LastAccessedDate]' \
--output table

Delete unused secrets:

aws secretsmanager delete-secret \
--secret-id my-unused-secret \
--recovery-window-in-days 30

Typical savings: $5-20/month

5. Unused KMS Keys

Active KMS keys cost $1/month each.

List KMS keys:

aws kms list-keys \
--query 'Keys[*].KeyId' \
--output text | while read key; do
aws kms describe-key --key-id $key \
--query 'KeyMetadata.{ID:KeyId,State:KeyState,Created:CreationDate}' \
--output text
done

Disable unused keys (can re-enable later):

aws kms disable-key --key-id arn:aws:kms:us-east-1:123456789012:key/xxxxx

Schedule key deletion (7-30 day waiting period):

aws kms schedule-key-deletion \
--key-id arn:aws:kms:us-east-1:123456789012:key/xxxxx \
--pending-window-in-days 30

Typical savings: $5-20/month

6. ECR Untagged Images

Untagged images from old builds accumulate.

Find untagged images:

aws ecr list-images \
--repository-name my-repo \
--filter tagStatus=UNTAGGED \
--query 'imageIds[*]' \
--output json

Delete untagged images:

repo="my-repo"
aws ecr list-images \
--repository-name $repo \
--filter tagStatus=UNTAGGED \
--query 'imageIds[*]' \
--output json | \
jq -r '.[] | .imageDigest' | while read digest; do
echo "Deleting $digest"
aws ecr batch-delete-image \
--repository-name $repo \
--image-ids imageDigest=$digest
done

Typical savings: $5-50/month

Summary: Total Hidden Cost Potential

Source Effort Monthly Savings
CloudWatch Logs retention 1 hour $10-100
S3 versioning lifecycle 1 hour $20-200
Lambda old versions 30 min $5-50
Orphaned Secrets 15 min $5-20
Unused KMS keys 15 min $5-20
ECR untagged images 30 min $5-50

Total potential savings: $50-440/month = $600-5,280/year

Next Steps

Start with CloudWatch Logs and S3 versioning - they typically have the biggest impact for the least effort.

Share the output of the audit commands and I’ll help you prioritize what to clean up first.