# Cloud bill audit — 14 days

Owner (the person who can delete, not the person who forwarded Cost Explorer): {name}
Account / org: {id}
Region of record: {eu-west-1 | eu-south-2 | other}
Invoice month: {YYYY-MM}
Invoice total (ex-VAT): {€}

A line with no owner is a deletion candidate, not a mystery.
Do not cut a line you cannot name. That is how the bill comes back.

---

## Day 0 — export, do not screenshot

- [ ] Cost Explorer, last full month, group by **Service**, unblended cost, CSV.
- [ ] Same month, group by **Usage type**, CSV.
- [ ] Tag coverage: what percentage of spend has `service` / `env` / `owner`? Write the number. If it is under 70%, tagging is week-2 work, not a blocker for week 1.

```bash
START=$(date -u -d "$(date +%Y-%m-01) -1 month" +%F)
END=$(date -u -d "$(date +%Y-%m-01)" +%F)

aws ce get-cost-and-usage \
  --time-period Start=$START,End=$END \
  --granularity MONTHLY \
  --metrics UnblendedCost \
  --group-by Type=DIMENSION,Key=SERVICE \
  --output json > ce-by-service.json
```

---

## Days 1–3 — the lines that exist even at zero traffic

### NAT Gateways

```bash
aws ec2 describe-nat-gateways \
  --filter Name=state,Values=available \
  --query 'NatGateways[].{Id:NatGatewayId,Vpc:VpcId,Subnet:SubnetId,AZs:AvailabilityZone}' \
  --output table
```

- [ ] Count of NAT Gateways: ____
- [ ] AZs they cover: ____
- [ ] VPC endpoints already present for S3 / DynamoDB / ECR: yes / no
- [ ] Decision: keep one / keep per-AZ / add endpoints and drop data-processing through NAT

### EKS control planes

```bash
for c in $(aws eks list-clusters --query 'clusters[]' --output text); do
  aws eks describe-cluster --name "$c" \
    --query 'cluster.{Name:name,Version:version,Status:status}' --output table
done
```

- [ ] Clusters: ____
- [ ] Any version on **extended support** ($0.60/h, not $0.10/h): ____
- [ ] Dev / staging clusters that run on Saturday night: ____

### Load balancers with no targets

Console or CLI: ALBs / NLBs whose target groups have zero healthy targets for 14 days.

- [ ] Idle load balancers: ____
- [ ] Deleted / scheduled: ____

### Public IPv4

```bash
aws ec2 describe-addresses \
  --query 'Addresses[].{PublicIp:PublicIp,Assoc:AssociationId,Alloc:AllocationId}' \
  --output table
```

- [ ] Idle Elastic IPs (no association): ____
- [ ] In-use public IPv4 that nothing public needs: ____

### Unattached EBS and old snapshots

```bash
aws ec2 describe-volumes --filters Name=status,Values=available \
  --query 'Volumes[].{Id:VolumeId,GiB:Size,Type:VolumeType,Created:CreateTime}' \
  --output table
```

- [ ] Unattached volumes, GiB: ____
- [ ] Snapshots older than the stated retention: ____
- [ ] gp2 volumes that should be gp3: ____

### CloudWatch log groups with no retention

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

- [ ] Log groups with infinite retention: ____
- [ ] Default retention set to 14 or 30 days: yes / no

---

## Days 4–8 — the lines that look like product

- [ ] Staging / dev compute that is on 24/7. Name the person who needs it at 03:00.
- [ ] RDS Multi-AZ on a non-production database.
- [ ] Node requests vs. p95 usage for 14 days (the gap is what you pay). See also: the cluster you can explain.
- [ ] NAT data that is S3 / ECR / DynamoDB and should have been a gateway endpoint.
- [ ] Observability ingest with no cardinality budget.

Do not "rightsize" a GPU or a Bedrock line because a survey said 29%. Name the workload first.

---

## Days 9–14 — cut, then prove it stayed cut

- [ ] Each deletion has a rollback (snapshot, previous instance type, re-create command).
- [ ] Cost Explorer, this week vs. last week, same services. The cut is a number, not a standup comment.
- [ ] A tag `owner` on every remaining line over €50/month.
- [ ] One paragraph to finance: what we cut, what we refused to cut, and why.

If week 3 the bill is back, you did not cut waste. You turned it off without an owner.
