Checks and budgets¶
muscles images check IMAGE checks an image against budgets and forbidden paths and exits 5 if any fails.

Configure¶
Put the budgets in .muscles.yaml next to your Dockerfile and commit it. muscles finds the nearest one by searching up from the working directory.
# .muscles.yaml
check:
max_size: 800MB # uncompressed
max_compressed: 300MB # what clients pull
max_waste: 50MB # or a share of the image, like "10%"
max_growth: 5% # vs --baseline; or bytes, like "50MB"
max_layers: 40
forbid: # must not exist in the final image
- "**/.env"
- "**/.git/" # a trailing / matches directories only
- "**/*.pem"
- "**/id_rsa"
allow: # exceptions to forbid
- "/etc/ssl/certs/*.pem"
Every budget can also come from a flag (--max-size 800MB) or an environment variable (MUSCLES_CHECK_MAX_SIZE=800MB). Flags win over the environment, which wins over the file. See Configuration.
Rules that aren't configured are skipped. With nothing configured, images check only reports warnings and exits 0.
The rules¶
| Rule | Fails when | Detail on failure |
|---|---|---|
size |
The uncompressed image is bigger than max_size. |
The biggest layer. |
compressed |
The compressed image is bigger than max_compressed. |
|
waste |
Waste is bigger than max_waste (bytes, or a percentage of the image size). |
The biggest waste, and why. |
growth |
The image grew more than max_growth compared with --baseline (bytes, or a percentage of the baseline). Skipped without --baseline. |
|
layers |
The image has more layers than max_layers. |
|
forbid |
A path in the final image matches a forbid pattern and no allow pattern. Up to five paths are listed per pattern. |
The layer that added it. |
Sizes accept SI units (KB, MB, GB: powers of 1000) and IEC units (KiB, MiB, GiB: powers of 1024).
How to fix¶
After the table, every failure and warning says what to change:
HOW TO FIX
✗ size: Run `muscles images inspect app.tar` for the fixes that save the most.
⚠ package cache (Dockerfile:5): End this RUN with `&& rm -rf /var/lib/apt/lists/*`.
⚠ forbid (L4): Keep it out of the build context with .dockerignore, or stop adding it in L4.
With --dockerfile (or dockerfile: in .muscles.yaml), results caused by one layer name its Dockerfile line, and in GitHub Actions their annotations are placed on that line.
Patterns¶
forbid and allow are glob patterns matched against every path in the final filesystem:
*matches within one path segment,**across any number of segments:**/.envmatches/.envand/app/config/.env.- A leading
/is optional:/app/*.keyandapp/*.keyare the same. - A trailing
/matches directories only:**/.git/matches the directory, not a file named.git. {a,b}and[abc]work as in shells.
Deleted paths are not in the final filesystem, so they don't match. A secret that was added in one layer and deleted in a later one is still in the image; muscles changes list IMAGE --type deleted shows those.
Warnings¶
The layer notes (Reading layers) are reported as warnings: package caches left behind, deletions that free nothing, permission-only rewrites and large files. They never fail the check. Turn them down in warn: settings.
Output¶
The JSON report has ok, failed, warnings and one result per rule with rule, status (pass, fail, warn, skip), message, detail, fix, and layer and dockerfile_line when a layer is at fault.
-o sarif writes the failures and warnings as a SARIF log for GitHub code scanning and other tools, each at the Dockerfile line or the .muscles.yaml budget responsible. See SARIF, and CI to upload it.