Getting started¶
muscles is one static binary. It needs no config file, no daemon and no Docker: point it at an image and it explains it.
Install¶
With Homebrew, on macOS or Linux:
With Go 1.26 or later:
Or download an archive for your platform from the releases page and put muscles on your PATH. Archives include shell completions and a man page. Releases after v0.0.4 are signed with cosign; to check one before you run it:
VERSION=0.1.0 # the release you downloaded
cosign verify-blob \
--bundle checksums.txt.sigstore.json \
--certificate-identity "https://github.com/rossedman/muscles/.github/workflows/release.yml@refs/tags/v${VERSION}" \
--certificate-oidc-issuer https://token.actions.githubusercontent.com \
checksums.txt
sha256sum --ignore-missing -c checksums.txt
--ignore-missing checks only the archives you downloaded. SECURITY.md also covers the container image and build provenance.
In CI, use the container image: ghcr.io/rossedman/muscles.
Your first image¶
Pick any image. A public one works without logging in:

That's the short way to run muscles images inspect: the image's size, what it costs to pull, how much of it is waste, and the fixes that save the most, each with the layer to change and what to change. If it's your own image, add --dockerfile Dockerfile and each fix names its line.
At a terminal, each command ends with a Next: line on stderr suggesting what to run next. Follow it and you'll walk from the summary down to the files.
Every layer¶

Each row is a layer, bottom to top:
| Column | Meaning |
|---|---|
# |
The layer's index. Commands like changes list --layer N use it. |
SIZE |
Uncompressed bytes the layer stores. |
+ ~ ◌ - |
Files added, modified, rewritten with the same contents, and deleted. |
STEP |
The Dockerfile instruction that made the layer. Dimmed rows are steps that made no layer (ENV, CMD, ...). |
NOTE |
The packages the layer installed, or where most of the bytes went, or a ⚠ warning worth acting on. |
The last line totals the image: uncompressed size, compressed size (what you pull) and waste.
Reading layers explains every column and note.
Look inside a layer¶

Directories whose changes are all alike are rolled up into one row. Add --files to see every file, --path /usr/local to narrow it down, or --type deleted to see only deletions.
Find the waste¶
Waste is bytes stored in a layer that the final image doesn't show, because a later layer overwrote, deleted or re-stored them. muscles IMAGE turns it into fixes; Finding waste explains each kind.
Compare two images¶
The first line is the answer in one sentence: how much it grew or shrank, in which step, the biggest file changes and the packages that changed. See Comparing images.
Where images come from¶
muscles reads the same image from wherever it is:
| You pass | muscles reads |
|---|---|
app:dev |
the local Docker (or Podman) daemon, if it has the image; otherwise the registry |
ghcr.io/acme/app:main |
the registry, streaming layers without pulling them |
app.tar |
a docker save tarball, or buildx --output type=docker,dest=app.tar |
./out or oci://./out:tag |
an OCI layout directory, or buildx --output type=oci |
Force one with a prefix (docker://, registry://, tar://, oci://) or --source. For multi-platform images, muscles picks Linux on your machine's architecture; choose another with --platform linux/arm64.
Registry credentials come from your Docker config and credential helpers, the same ones docker pull uses. If a registry says no, docker login to it.
Scripts and machines¶
Every command takes --json, and plain output (the default when stdout isn't a terminal) is one tab-separated record per line:
muscles layers list app:dev --json | jq '.image.waste'
muscles changes list app:dev --files -o plain | sort -t$'\t' -k3 -n | tail
See Output formats.
Next¶
- Put budgets in CI: CI and Checks and budgets.
- Every command and flag: CLI reference, or
muscles help <command>.