Find unused AWS resources and cut your AWS bill, without touching production.
aws-cost-cutter is a read-only Python (boto3) command-line tool that audits an
AWS account for cost waste and writes a prioritized savings report. It scans for
idle EC2, unattached EBS volumes, gp2 to gp3 upgrades, idle RDS, orphaned
snapshots, unassociated Elastic IPs, S3 lifecycle gaps, Savings Plan coverage
and utilization, and untagged spend, then ranks every finding by estimated
monthly saving.
It never creates, changes, or deletes anything. Every AWS call is a Describe,
List or Get, and a test enforces that. So it is safe to run on a production
or a client account on the first try.
$ aws-cost-cutter demo
AWS Cost Audit account 123456789012 2 region(s) lookback 14d
Estimated waste: $726.59/month (~$8,719.08/year) across 15 findings
Industry surveys put wasted cloud spend at roughly 25 to 35 percent of the bill, and the worst offenders are boring: a volume left behind after a migration, a database nobody connects to, snapshots no one ever deletes. This tool finds them in one pass so the expensive part, deciding what to actually change, is where the human time goes.
- The problem
- How it works
- What it finds
- Safe by design: read-only
- Sample output
- Install and run
- IAM permissions
- FAQ
- What is in here
- Design principles
Cloud bills grow by accretion. An engineer spins up a database to test something, a volume gets detached during a migration, a Savings Plan is sized to a workload that later moved. Each one is small. Together they are a fifth to a third of the bill, and nobody owns finding them, because it is tedious and the console makes you click through one service at a time, one region at a time.
A cost audit is mostly pattern matching across the whole account. That is what this tool automates, safely, so you spend your time on judgement instead of clicking.
A single run does five things in order:
- Builds a session. It uses your default credentials, a named
--profile, or assumes a read-only role in another account with--role-arn. The assume-role path is how you audit a client account without them handing over long-lived keys. - Discovers regions. With no
--regionsflag it enumerates every region the account has enabled and skips opt-in regions that would error onDescribecalls. You can pin it to one or more regions to go faster. - Runs each check. Every check paginates through the relevant
DescribeorListcalls across the target regions. A check that hits a permission gap or an unexpected response is caught and reported as a warning, so one locked-down service never sinks the whole run. - Decides idle from metrics, not guesses. Idle EC2 and RDS are judged from CloudWatch (average CPU, database connections) over a lookback window, default 14 days. When there is no metric data the tool says so and does not flag the resource, which keeps false positives off a client-facing report.
- Prices and ranks. Each finding gets a list-price monthly estimate, and the report sorts by money so you fix the biggest waste first. Output is a terminal table, a markdown report, or JSON.
flowchart LR
A["Session or assumed role"] --> B["Enabled regions"]
B --> C["Resource checks (EC2, EBS, RDS, S3)"]
B --> D["Cost Explorer checks (opt-in)"]
C --> E["CloudWatch metrics for idle detection"]
E --> F["Findings priced and ranked by monthly saving"]
D --> F
F --> G["Report: terminal, markdown, JSON"]
The full catalogue of checks, the exact AWS calls each one makes, and where each can be wrong is in docs/checks.md. The estimate method and its caveats are in docs/methodology.md.
Each scanner maps to a specific, well-known source of AWS waste.
Volumes left in the available state are attached to nothing and still bill
every month. A mid-size account often carries several. The tool flags them with
high confidence and prices the full storage cost as the saving.
In-use gp2 volumes are flagged for migration to gp3, which is cheaper per GB and includes a free 3,000 IOPS / 125 MB/s baseline. The change is in place, with no downtime and no snapshot required.
Running instances averaging under 3 percent CPU over the lookback window are flagged as idle rather than lightly loaded. Stopped instances are flagged separately, because their attached EBS keeps billing even when compute does not.
Databases with effectively no connections over the lookback window are surfaced. Connections are a cleaner idle signal than CPU, which stays low on a healthy but quiet database. Multi-AZ cost is accounted for.
Self-owned snapshots whose source volume no longer exists are flagged. Snapshots that back an AMI are excluded, because deleting them would break the image, and snapshots owned by another account are ignored.
Since February 2024 AWS bills every public IPv4 address, associated or not, so an allocated but unassociated Elastic IP is pure waste (about 3.65 USD per month each). The tool lists them all.
Buckets holding measurable Standard storage with no lifecycle policy are flagged as candidates for Standard-IA or Intelligent-Tiering. Incomplete multipart uploads, which bill as storage forever and are invisible in the console object list, are surfaced too.
With --with-cost-explorer, the tool reports low commitment coverage (steady
on-demand spend that a Savings Plan would discount) and low utilization (already
committed spend going unused). Target coverage is usually 70 to 80 percent.
Also via Cost Explorer, it reports the share of spend missing a chosen cost allocation tag. This is a governance finding, not a quick win, so it carries a zero-dollar saving on purpose and never inflates the headline number.
- Read-only, and enforced. Every call is a
Describe,ListorGet. There is no apply, fix or delete path anywhere in the code. A test, tests/test_readonly_guard.py, records every AWS call during a full scan and fails CI if any of them mutates state. - Least privilege. Ship the scoped read-only policy in iam/cost-audit-readonly.json instead of admin.
- Cross-account the clean way. The account owner creates a read-only role
that trusts you, and you pass
--role-arn. Nothing of yours is exposed, and nothing of theirs persists after the session. - Nothing leaves the machine. AWS calls in, a local report out. No telemetry, no third-party service.
More detail in docs/safety.md.
A full, reproducible report lives at
samples/sample-report.md (and .json). It is
generated by make demo against a synthetic account, so it is not a mockup. The
executive summary:
| Category | Findings | Est. monthly |
|---|---|---|
| Savings Plans | 2 | $474.00 |
| RDS | 1 | $124.83 |
| EC2 | 3 | $65.19 |
| EBS | 6 | $46.82 |
| S3 | 2 | $15.75 |
| Tagging | 1 | $0.00 |
Below the summary, every finding gets its own block: the check, severity and confidence, the estimated saving, why it was flagged, and the exact fix.
Requires Python 3.9 or newer.
git clone https://github.com/julliuzsam/aws-cost-cutter.git
cd aws-cost-cutter
make install # editable install with dev tools
make demo # synthetic account, no credentials, no AWS billAgainst a real account (read-only):
# Default profile, all enabled regions, terminal output
aws-cost-cutter scan
# Save a markdown report
aws-cost-cutter scan --format markdown -o report.md
# A specific profile and region, faster
aws-cost-cutter scan --profile prod --regions eu-west-1,us-east-1
# Add the Cost Explorer checks (needs CE enabled, bills about 0.01 USD per call)
aws-cost-cutter scan --with-cost-explorer
# Audit a client account through an assumed read-only role
aws-cost-cutter scan --role-arn arn:aws:iam::111122223333:role/cost-auditOutput formats: console (default), markdown, json. Idle lookback is set
with --lookback-days.
The tool needs only read access. Use the scoped policy at
iam/cost-audit-readonly.json, which grants the
specific Describe, List and Get actions the checks call, plus optional
ce:Get* for the Cost Explorer checks. Prefer it over the broad AWS-managed
ReadOnlyAccess. If you skip --with-cost-explorer, you can drop the Cost
Explorer statement entirely.
Yes. The tool is read-only by construction, holds no write or delete permissions, and a test in the repo enforces that every call is a read. Scope it further with the least-privilege policy provided.
Yes, that is the design goal. It never mutates anything, paginates and retries politely, and isolates per-check failures. For a client, use the assume-role flow so they grant temporary read-only access.
Only the read actions in iam/cost-audit-readonly.json. Cost Explorer access is optional and separable.
That depends entirely on the account. Industry surveys put typical waste at 25 to 35 percent of spend, but the only honest answer is to run it and read the report. The estimates are list-price starting points to confirm against your actual Cost and Usage Report.
Those are dashboards. This is a single command that does resource-level waste detection across every region and hands you one prioritized, actionable report, including findings (orphaned snapshots, incomplete multipart uploads) that the built-in tools do not surface as cleanly. It complements them, it does not replace the Cost and Usage Report.
No. It only reads and reports. You decide what to change.
Almost always. gp3 is cheaper per GB and ships a free performance baseline. The tool flags every in-use gp2 volume with the estimated saving.
Since February 2024 AWS bills all public IPv4 addresses, so an unassociated Elastic IP costs about 3.65 USD per month. The tool lists every one.
| Path | What it is |
|---|---|
src/aws_cost_cutter/checks/ |
One module per check, plus the registry |
src/aws_cost_cutter/session.py |
Session, assume-role, region discovery, retries |
src/aws_cost_cutter/pricing.py |
Static price map and savings estimators |
src/aws_cost_cutter/report.py |
Markdown, JSON and terminal renderers |
src/aws_cost_cutter/demo.py |
Synthetic account behind make demo |
iam/cost-audit-readonly.json |
Least-privilege policy |
samples/ |
The committed demo report |
docs/ |
Methodology, check catalogue, safety, limitations |
tests/ |
moto and Stubber tests, including the read-only guard |
- Read-only is the product. Trust is the whole pitch. An audit tool you have to sandbox is not an audit tool.
- Rank by money, not by count. A hundred low findings matter less than one idle Multi-AZ database. The report sorts on estimated saving.
- Be honest about estimates. List-price approximations, clearly labelled, beat false precision. Confidence and methodology travel with every finding.
- No false positives on idle. Decide idle from metrics, and when there is no data, say nothing rather than guess.
- Safe on a stranger's account. Pagination, adaptive retries, per-check error isolation, assume-role.
Built by Jelle Samaey. Available for freelance: AWS cost optimization and FinOps consulting. Licensed under MIT.