Release v0.2.0 · Image on GHCR
Your cluster knows what each pod requested. It also knows what each pod actually used. cloudtrim puts the two side by side, suggests request values that fit the real numbers, and tells you roughly what the difference costs you every month.
Quickstart
pipx install git+https://github.com/SachuJames/cloudtrim.git
cloudtrim --from-file examples/sample-snapshot.json
That runs against a small sample cluster that ships with the repo, twelve containers across three namespaces, a few of them badly over-provisioned. This is the tool's real output:
prod-eks-1 · source file examples/sample-snapshot.json · windows: snapshot · 120 samples · span unknown
platform
>> worker-8d2f6a4c9-h3j5/worker cpu 4,000m p95 700m -> 910m mem 8Gi p95 2,100Mi -> 2,736Mi over waste $165.28
suggested limits: cpu 1,370m, mem 4,112Mi
>> report-3e7b1d5f8-s6r4/report cpu 500m p95 90m -> 120m mem 1Gi p95 300Mi -> 400Mi over waste $19.90
suggested limits: cpu 180m, mem 608Mi
! scheduler-9c5a3b7e2-v1x9/scheduler cpu 100m p95 88m -> 120m mem 128Mi p95 70Mi -> 128Mi tight waste $0.00
suggested limits: cpu 180m, mem 192Mi
subtotal waste $185.17/mo
shop
>> checkout-5c8a7e2d1-m4k8/checkout cpu 2,000m p95 380m -> 500m mem 2Gi p95 600Mi -> 784Mi over waste $67.60
suggested limits: cpu 750m, mem 1,184Mi
>> api-6d9f8c5b4-x2v1/api cpu 1,000m p95 225m -> 300m mem 1Gi p95 495Mi -> 656Mi over waste $29.52
suggested limits: cpu 450m, mem 992Mi
>> token-4a9c2e8b6-t5n7/token cpu 100m p95 40m -> 60m mem 128Mi p95 55Mi -> 80Mi over waste $1.93
suggested limits: cpu 90m, mem 128Mi
web-7f4b9d6c8-p9q3/web cpu 500m p95 325m -> 500m mem 512Mi p95 400Mi -> 512Mi ok waste $0.00
! auth-6b3d5f9a7-z8w2/auth cpu 250m p95 228m -> 300m mem 256Mi p95 180Mi -> 256Mi tight waste $0.00
suggested limits: cpu 450m, mem 384Mi
subtotal waste $99.05/mo
batch
>> exporter-7a4c8e2b5-n8q1/exporter cpu 50m p95 12m -> 20m mem 64Mi p95 20Mi -> 32Mi over waste $1.41
suggested limits: cpu 30m, mem 48Mi
etl-2f8d4c6a1-b7m3/etl cpu 1,500m p95 1,250m -> 1,500m mem 3Gi p95 1,900Mi -> 3Gi ok waste $0.00
! cache-5b1e9d3f7-k2p6/redis cpu 200m p95 150m -> 200m mem 512Mi p95 470Mi -> 624Mi tight waste $0.00
suggested limits: cpu 300m, mem 944Mi
! ingest-1d6f3a9c4-w4t8/ingest cpu 800m p95 700m -> 910m mem 1Gi p95 600Mi -> 1Gi tight waste $0.00
suggested limits: cpu 1,370m, mem 1,536Mi
subtotal waste $1.41/mo
12 containers | cpu 11,000m -> 5,440m | mem 17,984Mi -> 10,304Mi | waste $285.64/mo (49.7% of requested)
note: tight containers would add $7.75/mo if their suggestions were applied
note: rightsized pods do not shrink the nodes under them; unallocated node capacity is a separate cost
pricing: estimates only, on-demand list price, us-east-1, as of 2026-10
$285 a month going to containers that asked for far more than they use.
One command, no account, no cloud bill involved. The >> lines are
where the money is; ! marks the tight ones. On a terminal they are
colored too (--no-color turns that off).
How it decides
CPU and memory are judged separately, because a container can waste CPU and starve on memory at the same time.
- over: the request is more than twice the p95 usage. The suggestion is p95 plus 30 percent headroom, rounded up to a manifest-sensible step (10m of CPU, 16Mi of memory), floored at 10m/16Mi, and never raised above the current request.
- tight: p95 usage sits above 85 percent of the request. A raise to p95 times 1.3 is suggested, but it is reported as extra cost, not folded silently into the savings.
- ok: close enough to leave alone.
- no-data: no samples at all. These get their own section at the end of the report and are left out of every total, because a container nobody measured should not dilute the numbers.
Every suggestion comes with a suggested limit: the suggested request times 1.5.
The defaults are a starting point, not a doctrine. Risk tolerance varies, so all three knobs move:
cloudtrim --from-file snapshot.json --headroom 1.5 --over-factor 3 --tight-ratio 0.8
When usage arrives in more than one window (Prometheus mode, below), the longest window decides the verdict. The shorter one is printed next to it, and when its p95 runs far above the long window's, the container is called out as bursty: a workload that spikes is a different right-sizing problem than one that is steady.
Feeding it data
Three ways in, in increasing order of how much history they carry.
Prometheus is the one with real history:
cloudtrim --from-prometheus http://localhost:9090
It pulls CPU and memory usage as range queries over two windows, the
last 24 hours at a 5 minute step and the last 7 days at a 30 minute
step (--windows 7d fetches just one, --windows 24h,7d,30d is the
shape if you want more). The requests come from kube-state-metrics
(kube_pod_container_resource_requests), so that exporter needs to be
scraped by your Prometheus; if the requests series are missing,
cloudtrim says so and stops rather than guessing. The first line of
the report states the source, the windows, the sample count, and the
span covered, so you can see how much evidence a verdict stands on.
A snapshot file is one JSON object with the cluster name, a timestamp, and a list of containers:
{
"cluster": "prod-eks-1",
"captured_at": "2026-10-18T09:30:00Z",
"containers": [
{
"namespace": "shop", "pod": "api-6d9f8c5b4-x2v1", "container": "api",
"cpu_request_m": 1000, "mem_request_mi": 1024,
"cpu_limit_m": 2000, "mem_limit_mi": 2048,
"labels": {"app": "api"},
"samples": [{"cpu_m": 210, "mem_mi": 480, "t": 1791859200}]
}
]
}
Limits and labels are optional; a sample's t (epoch seconds) is too,
and when samples carry timestamps the report can state the span they
cover. Malformed entries fail fast and name the entry, so a bad
exporter feed does not silently poison the numbers. If you need
snapshots written for you, examples/exporter/ has a small script
plus a CronJob that writes one hourly from kubectl and metrics-server.
A live cluster goes through kubectl:
cloudtrim --from-cluster
Requests and limits come from the pod spec, usage from metrics-server. That yields exactly one sample per container, right now, not a week of history. Take single-sample answers as a first look, not a verdict.
Filters narrow any of the three sources down before the math:
cloudtrim --from-prometheus http://localhost:9090 --namespace shop
cloudtrim --from-file snapshot.json --selector app=api --selector team=payments
(Selector filtering matches container labels. File snapshots carry the labels written into them, kubectl mode uses the pod's labels, and Prometheus mode uses whatever labels the series carry.)
Getting values out: the dry run
A report is advice; --apply turns it into a starting diff. It prints
a snippet and stops. Nothing is applied, ever.
cloudtrim --from-file snapshot.json --apply # Helm-style values
cloudtrim --from-file snapshot.json --apply --apply-format kustomize
cloudtrim --from-file snapshot.json --apply --apply-format patch
- values: suggested requests and limits grouped by namespace, pod, and container, as YAML.
- kustomize: a kustomization skeleton plus a patch file with one entry per flagged pod. Pod names come from the snapshot, hash suffix and all; point each entry at the workload that owns the pod.
- patch: a JSON merge patch per pod. Merge patches replace the whole containers list, so every container of the pod is included, flagged ones with their suggestions and the rest with what they already have.
Watching it move: diff
cloudtrim --from-file today.json --diff last-week.json
Per container: the verdict then and now, the waste then and now.
Containers only on one side are marked new or gone, and the last line
is the totals delta. With --format json the comparison rides along
in a diff section of the normal report.
The cost model
Each region has a per vCPU-hour and per GiB-hour rate built in, and the monthly number is just the hourly cost times 730 hours. The built-ins are on-demand list-price approximations for us-east-1, us-west-2, eu-west-1, and ap-south-1, dated 2026-10. They are there to make the waste concrete, not to predict your bill.
Pay less than list? Say how much less:
cloudtrim --from-file snapshot.json --discount 0.6
That multiplies every price by 0.6, the rough shape of a savings plan or reserved discount, and the pricing line in the report says the factor out loud. Or bring your own rates entirely:
cloudtrim --from-file snapshot.json --pricing-file my-rates.json
A pricing file is JSON with vcpu_hour, gib_hour, and optionally
as_of.
Reports and CI
Text goes to stdout by default, grouped by namespace with a subtotal each. The other formats:
cloudtrim --from-file snapshot.json --format json > report.json
cloudtrim --from-file snapshot.json --format markdown # paste into an issue
The JSON is versioned ("version": "2") and carries per-container
windows, suggested limits, per-namespace rollups, and the totals, so
other tools can build on it.
In CI, gate on the waste share of requested cost:
cloudtrim --from-file snapshot.json --fail-on-waste-pct 25
Exits 1 when waste is over 25 percent, so a PR that doubles a request gets caught the same way a failing test does. For GitHub Actions there is an action in this repo:
- uses: SachuJames/cloudtrim@v0.2.0
with:
snapshot: snapshots/prod.json
fail-on-waste-pct: "25"
For CI and air-gapped setups in general, the container image is the
recommended route: ghcr.io/sachujames/cloudtrim:v0.2.0, no Python
setup on the runner at all.
Honest limits
The answers are only as good as the samples. A snapshot from an idle weekend will tell you to cut everything; a snapshot from a deploy storm will tell you to raise everything. Prometheus mode widens the window, but it is still bounded by what your Prometheus kept: default retention is often 15 days, and the steps (5 minutes, 30 minutes) smooth over spikes shorter than themselves, so very spiky workloads look calmer than they are.
The dollars are estimates. On-demand list prices in four regions,
dated, nothing more. Reserved instances, savings plans (the
--discount factor is a blunt stand-in for both), spot pricing, and
the fact that bins are packed on nodes all move the real number.
Rightsizing pods does not shrink the nodes under them, either; the
report says that out loud when it finds waste.
And the obvious one: cloudtrim reads and reports. It never touches a cluster. Every suggestion is a line in a report, or a dry-run snippet, for a human to apply.
Layout
src/cloudtrim/
cli.py argument parsing and the exit codes
snapshot.py snapshot validation, plus the kubectl reader
prometheus.py range-query reader for usage history and requests
analyze.py p95, verdicts, and the suggestions
pricing.py built-in rates, --pricing-file, --discount
report.py text, markdown, and JSON output
apply.py the --apply dry-run snippets
diff.py comparing two snapshots
tests/ the suite, 72 cases, Prometheus covered with recorded fixtures
examples/ sample-snapshot.json, and the exporter CronJob example
action.yml the GitHub Action
No runtime dependencies, standard library only.
Install
PyPI is coming under the name cloudtrim-cli (the plain name belongs to
someone else's package). Until then:
pipx install git+https://github.com/SachuJames/cloudtrim.git
or download the wheel from the
v0.2.0 release
and pip install cloudtrim_cli-0.2.0-py3-none-any.whl. A container image
is on GHCR as ghcr.io/sachujames/cloudtrim:v0.2.0.
License
MIT. See LICENSE.
Metadata
Release files for cloudtrim-cli 0.2.1
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | Provenance | Actions |
|---|---|---|---|---|
| cloudtrim_cli-0.2.1.tar.gz | 34.4 kB | With provenance | Details for cloudtrim_cli-0.2.1.tar.gz |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Actions Reset |
|---|---|---|---|---|
| cloudtrim_cli-0.2.1-py3-none-any.whl | Python 3 | none | any | Details for cloudtrim_cli-0.2.1-py3-none-any.whl |
Total release size: 60.3 kB
Release files / cloudtrim_cli-0.2.1.tar.gz
| Download URL | cloudtrim_cli-0.2.1.tar.gz |
|---|---|
| Size | 34.4 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
88bd819ac4e72a4d11fabff72257b21576cc9548269abb1c5b6d5640921f7605
|
|
BLAKE2b-256 checksum How to use checksums |
81be9b5bd587203e654a5b7ff48c6b3ca4a6140bd03845eee78463b1a1b1f890
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.14
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Oct 20, 2026.
Transparency logRelease files / cloudtrim_cli-0.2.1-py3-none-any.whl
| Download URL | cloudtrim_cli-0.2.1-py3-none-any.whl |
|---|---|
| Size | 25.8 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
e4c6c6413f3f31fb58991b08646bd5456c559426bca51459eb5c95d3ebdc4ee0
|
|
BLAKE2b-256 checksum How to use checksums |
127e2e0a5a6d50f75372c4394fccc6d44d28a7eefaa72acd10bad475d0f785bf
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.14
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Oct 20, 2026.
Transparency logSecurity for cloudtrim-cli 0.2.1
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
Full provenance
All 2 files in this release have been uploaded with provenance metadata.
Attestation for cloudtrim_cli-0.2.1.tar.gz
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Oct 20, 2026.
Transparency logA note on package security
PyPI is a repository for distributing packages, not a service for auditing them. While third-party security researchers and our staff do their best to detect and remove malicious packages, PyPI cannot guarantee the quality, security or suitability of any given package.
To learn more about protecting yourself from malicious actors, visit our package security documentation.