Skip to main content
cloudtrim

CI Release Python License

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)

Source distribution for cloudtrim-cli 0.2.1
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)

Table of built distributions (wheels) for cloudtrim-cli 0.2.1
File Interpreter ABI Platform Actions
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 log

Release 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 log

Security 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.

Select from 2 files

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 log

A 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.

Release history Release notifications | RSS feed

This release

0.2.1 This release

2 release files Full provenance

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page