Interactive Docker image layer inspector with CI-friendly efficiency checks. Single binary; no daemon required when reading saved image archives.
A terminal tool for understanding what's inside a Docker image — which layer added each file, where the wasted bytes are, and what your RUN steps actually produced.
Use it to:
- Debug bloated images and find the layer responsible
- Review the filesystem impact of a Dockerfile change before merging
- Gate CI on layer waste or efficiency thresholds
# macOS / Linux
brew install deveshctl/tap/layerx
# Inspect a Docker image (daemon required)
layerx nginx:latest
# Or inspect a local docker-save / OCI archive (no daemon needed)
layerx ./build/app.tarOther platforms: see Install.
| Mode | Command | Best for |
|---|---|---|
| Interactive | layerx IMAGE_OR_ARCHIVE |
Exploring layers, diffs, file contents, wasted bytes |
| CI | layerx ci IMAGE_OR_ARCHIVE or CI=true layerx ... |
Pipeline gates on efficiency / wasted bytes |
| Compare | layerx compare OLD NEW |
Side-by-side deltas between two builds; CI regression gate |
| Export | layerx --json out.json IMAGE_OR_ARCHIVE |
Scripts, dashboards, jq |
IMAGE_OR_ARCHIVE is auto-detected: an existing file is read directly without contacting any container runtime, anything else is resolved through the Docker daemon. All three modes accept either form.
Deeper guides live in docs/:
- Configuration reference — every
.layerx.yamlfield, both path-rules forms, starter flavours - CI integration — GitHub Actions and GitLab CI recipes, threshold recommendations, exit codes
- JSON export — full schema,
jqone-liners, scripting use cases
- Browse layers with vim keys; see Dockerfile command, size, short digest
- Per-layer file tree with diff colouring (green = added, yellow = modified, red = removed)
- Open files inline with line numbers, scrolling, and in-viewer search
- Sort by size, filter by name, hide unchanged files
- Extract a file to disk, copy a path, or copy file contents to your clipboard (works over SSH and tmux)
- Efficiency score and wasted bytes always visible in the status bar
See TUI keybindings for the full shortcut list.
- Three configurable rules: lowest efficiency, highest wasted bytes, highest user-wasted percent
- Exits
0on pass,1on rule failure,2on internal error - Configurable via
.layerx.yamlor CLI flags
# Compare a release tag against the previous one
layerx compare myapp:1.4.0 myapp:1.5.0
# Mix archives and refs freely
layerx compare ./build/prev.tar myapp:next
# Show every diff entry instead of the top-N summary
layerx compare --mode full myapp:old myapp:new- Reports size, efficiency, layer, file, and waste deltas in a deterministic text report with consistently aligned columns
- Default compact mode shows the largest deltas per section with a
... and N morecounter;--mode fullprints everything;--mode summarykeeps only the header and verdict - Last line is always machine-parseable:
verdict: ok,verdict: regression reason=efficiency,waste,verdict: noop digest=<sha256>when both sides resolve to the same image, orverdict: noop reason=path-equalwhen both arguments are the same archive path and no digest is observable - Exit codes:
0no regression,1regression detected,2operational error (daemon down, archive missing, etc.) - Live progress on stderr while resolving remote images — pulling, exporting, parsing, and the resolved digest are all surfaced per side. Pipe
2>/dev/nullto silence; stdout stays grep-clean for CI gating - Running
layerx comparewith no arguments prints a short usage hint with concrete examples instead of an opaque error
Full analysis (layers, files, efficiency) as JSON — pipe through jq for scripted checks. See docs/json-export.md for the full schema, jq one-liners, and scripting recipes.
Docker is required when inspecting Docker image references (nginx:latest, myregistry/app:tag). It is not required when inspecting a local archive file (docker save output or OCI layout tarball) — archive mode reads the file directly.
| Platform | Install |
|---|---|
| Linux | Docker Engine |
| macOS | Docker Desktop |
| Windows | Docker Desktop |
brew install deveshctl/tap/layerxscoop bucket add layerx https://github.com/deveshctl/scoop-bucket
scoop install layerx# amd64
curl -LO https://github.com/deveshctl/layerx/releases/latest/download/layerx_linux_amd64.deb
sudo dpkg -i layerx_linux_amd64.deb
# arm64
curl -LO https://github.com/deveshctl/layerx/releases/latest/download/layerx_linux_arm64.deb
sudo dpkg -i layerx_linux_arm64.deb# amd64
curl -LO https://github.com/deveshctl/layerx/releases/latest/download/layerx_linux_amd64.rpm
sudo rpm -i layerx_linux_amd64.rpm
# arm64
curl -LO https://github.com/deveshctl/layerx/releases/latest/download/layerx_linux_arm64.rpm
sudo rpm -i layerx_linux_arm64.rpmPrebuilt binaries for Linux, macOS, and Windows (amd64 + arm64) on the Releases page. For a specific version, replace latest with the tag (e.g. v1.3.0) in the URLs above.
Requires Go 1.26+:
go install github.com/deveshctl/layerx@latest# Interactive TUI — Docker reference
layerx nginx:latest
# Interactive TUI — local archive (no daemon required)
layerx ./build/app.tar
# Force a fresh analysis (bypass the cache)
layerx --no-cache nginx:latest
# CI mode — exit 1 if efficiency < 95% (works for both inputs)
layerx ci --lowest-efficiency 0.95 nginx:latest
layerx ci --lowest-efficiency 0.95 ./build/app.tar
# JSON export
layerx --json analysis.json nginx:latest
# Shell completion (bash)
source <(layerx completion bash)| Key | Action |
|---|---|
Tab |
Switch panel (layers ↔ file tree) |
j / k |
Move up / down |
g / G |
Jump to top / bottom |
Enter |
Open file viewer; expand or collapse a folder |
Esc |
Dismiss (close search → close viewer → close waste → clear filter → close help). Quits only on the loading and error screens. |
/ |
Filter file tree (tree) / search in viewer (viewer) |
n / N |
Next / previous search match (viewer) |
y |
Copy file path to clipboard |
Y |
Copy file content (viewer) or layer command (layers) |
d |
Toggle diff-only mode (hide unchanged files) |
s |
Cycle sort: default → largest → smallest |
S |
Cycle layer size column: change → stored → stored+change |
w |
Toggle wasted-files overlay (Enter jumps to introducing layer) |
x |
Extract focused file to disk |
? |
Toggle help overlay |
q |
Quit |
- name: Install layerx
run: |
curl -LO https://github.com/deveshctl/layerx/releases/latest/download/layerx_linux_amd64.deb
sudo dpkg -i layerx_linux_amd64.deb
- name: Check image efficiency
run: layerx ci --lowest-efficiency 0.95 myapp:${{ github.sha }}Drop a .layerx.yaml in your project root:
version: 1
rules:
lowest-efficiency: 0.9
highest-wasted-bytes: 52428800 # 50MB
highest-user-wasted-percent: 0.1
path-rules:
block:
- "**/.git/**"
- /tmp/**
deny-waste:
- "**/*.pyc"See layerx init (below) for ready-made configs by language.
CLI flags override config-file values. Setting a threshold to 0 or negative disables that rule.
For the full field reference, path-rule semantics, and worked examples, see docs/configuration.md. For end-to-end CI/CD recipes (GitHub Actions, GitLab CI, threshold recommendations, exit-code reference), see docs/ci-integration.md.
| Code | Meaning |
|---|---|
0 |
All rules passed (or, for non-CI commands, the run completed). |
1 |
A CI rule failed (layerx ci), or layerx compare detected a regression. |
2 |
Operational error — Docker daemon down, archive missing, malformed config, write failure, etc. Don't gate on this; surface it. |
Pipelines should treat 1 as the gate signal and fail loudly on 2. Full breakdown in docs/ci-integration.md.
Run layerx init to drop a ready-made .layerx.yaml in your repo:
layerx init --flavour node # Node.js / npm / yarn / pnpm
layerx init --flavour python # CPython, .pyc and __pycache__ rules
layerx init --flavour java # Maven, Gradle, multi-stage targets
layerx init --flavour go # tighter thresholds for Go images
layerx init --flavour generic # baseline — works for any stackEach starter blocks build-time caches (/root/.npm/..., /root/.cache/pip/...,
etc.) and version-control metadata, and flags wasteful layer patterns
(node_modules reinstalled per layer, .pyc files duplicated). Edit the
file after init to tune for your repo.
The starter configs live in cmd/examples/ for browsing
or copy-paste.
| Variable | Purpose |
|---|---|
CI=true |
Treat layerx IMAGE (no subcommand) as layerx ci IMAGE |
LAYERX_CACHE_DIR |
Override the default analysis cache directory |
LAYERX_CACHE_TTL_DAYS |
Evict cache entries older than this many days. Default 30. 0 disables. |
LAYERX_CACHE_MAX_BYTES |
Evict oldest entries until total cache size is at or below this. Default 1073741824 (1 GiB). 0 disables. |
Repeat runs against an unchanged image digest reuse the cache and skip the tar export and parse. --no-cache (alias --refresh) bypasses the cache for a single run; the run still refreshes the cache on success. The cache directory self-prunes by age and total size at the end of every successful write; failures are best-effort and surface as cache prune ... warnings on stderr.
- "Cannot connect to the Docker daemon" — Docker isn't running. Start Docker Desktop, or
sudo systemctl start dockeron Linux. (Tip: if your image is already saved as a tarball, pass the file path instead — no daemon required.) - "Archive not found" — the path you passed doesn't exist or isn't a regular file. Check spelling and that you're not pointing at a directory.
- "Not a valid image archive" — the file exists but isn't a
docker saveor OCI layout tarball. Re-export withdocker save -o image.tar IMAGEor build with--output type=oci,dest=image.tar. - "image not found" — layerx pulls images on demand. Check the reference and that you can
docker pullit manually. - Cache permission errors — point
LAYERX_CACHE_DIRsomewhere writable, e.g.LAYERX_CACHE_DIR=$HOME/.cache/layerx.
Issues and PRs welcome. For larger changes, please open an issue first to discuss the approach. See CONTRIBUTING.md for the build, test, and branching workflow, and CHANGELOG for release notes.
Security issues: please follow SECURITY.md — don't open public issues for vulnerabilities.
image/ Domain layer — Docker SDK, tar parsing, file tree, efficiency
tui/ Bubbletea v2 TUI — consumes image/ interfaces only
ci/ CI evaluator — consumes image/ interfaces only
cmd/ Cobra CLI — wires packages together
config/ .layerx.yaml loader
Design rules:
image/has zero imports fromtui/,ci/, orconfig/- TUI and CI consume interfaces, never concrete Docker SDK types
- All Docker client calls negotiate the API version (no breakage on Docker Engine upgrades)
- Both whiteout conventions handled correctly (regular and opaque)
| Concern | Choice |
|---|---|
| Language | Go 1.26+ |
| CLI | cobra |
| TUI | bubbletea v2 + lipgloss v2 + bubbles v2 |
| Docker | moby/moby client |
| Config | goccy/go-yaml |
| Testing | testify |
MIT