dive is a terminal tool for exploring a Docker or OCI image one layer at a time and discovering wasted space so you can make your images smaller. You point it at an image, and it opens an interactive two-pane view: the image's layers on the left, and the combined file tree on the right. As you move between layers, you see exactly which files each Dockerfile instruction added, changed, or removed — and an "efficiency" score that estimates how much space is being wasted by files that are duplicated, moved, or deleted across layers.
It runs in two modes:
- Interactive mode — a full-screen terminal UI (TUI) for browsing layers and files by hand.
- CI mode — no UI; analyze an image and return a pass/fail exit code based on efficiency rules, so a pipeline can block bloated images.
Note
divedoes not change your images. It reads them (from a container engine or a tar archive), analyzes them in memory, and renders the result. Nothing is written back to the image.
1. Quick Start
Analyze an existing image by tag, ID, or digest:
dive <your-image-tag>
For example:
dive nginx:latest
● Loading image... done
● Analyzing image... done
(the interactive UI opens)
When the UI opens:
- Use ↑/↓ to move through layers (left pane).
- Press Tab to jump to the file tree (right pane) and explore the filesystem at that layer.
- Press Ctrl+C (or Q) to quit.
Tip
No image on your machine yet?
divewill pull it through your container engine first, the same waydocker runwould. Large images can take a while to fetch —diveprintsthis can take a while for large images...after a few seconds so you know it is still working.
Build and analyze in one step
If you are iterating on a Dockerfile, you can build the image and drop straight into analysis without a separate docker build:
dive build -t my-app:latest .
This is a thin wrapper around docker build — replace docker build with dive build and pass the exact same arguments.
Analyze in CI (no UI)
Set CI=true (or pass --ci) to skip the UI and get a pass/fail result instead:
CI=true dive my-app:latest
Analysis:
efficiency: 98.45 %
wastedBytes: 1485633 bytes (1.5 MB)
userWastedPercent: 12.00 %
Evaluation:
PASS highestUserWastedPercent
FAIL lowestEfficiency (image efficiency is too low (efficiency=0.98 < threshold=...))
FAIL [pass:1 fail:1]
A failing rule makes dive exit with a non-zero status, which fails the pipeline step.
2. Installation
dive ships as a single self-contained binary for Linux, macOS, and Windows. Pick the method that matches your platform.
Important
diveanalyzes images through a container engine (Docker or Podman) or from a tar archive. For the default Docker workflow you need a working Docker engine that your user can talk to. See Requirements below.
Ubuntu / Debian (.deb)
DIVE_VERSION=$(curl -sL "https://api.github.com/repos/wagoodman/dive/releases/latest" | grep '"tag_name":' | sed -E 's/.*"v([^"]+)".*/\1/')
curl -fOL "https://github.com/wagoodman/dive/releases/download/v${DIVE_VERSION}/dive_${DIVE_VERSION}_linux_amd64.deb"
sudo apt install ./dive_${DIVE_VERSION}_linux_amd64.deb
RHEL / CentOS / Fedora (.rpm)
DIVE_VERSION=$(curl -sL "https://api.github.com/repos/wagoodman/dive/releases/latest" | grep '"tag_name":' | sed -E 's/.*"v([^"]+)".*/\1/')
curl -fOL "https://github.com/wagoodman/dive/releases/download/v${DIVE_VERSION}/dive_${DIVE_VERSION}_linux_amd64.rpm"
rpm -i dive_${DIVE_VERSION}_linux_amd64.rpm
Arch Linux
pacman -S dive
macOS (Homebrew)
brew install dive
macOS (MacPorts)
sudo port install dive
Windows (Chocolatey)
choco install dive
Windows (Scoop)
scoop install main/dive
Windows (winget)
winget install --id wagoodman.dive
Nix / NixOS
# NixOS
nix-env -iA nixos.dive
# non-NixOS (Linux, Mac)
nix-env -iA nixpkgs.dive
Go tools
go install github.com/wagoodman/dive@latest
Caution
Snap users, read this. A Snap package exists (
sudo snap install dive), but it is not recommended if you installed Docker viaapt-get— it can break your existing Docker daemon. The Snap also requires manual interface connections:sudo snap install docker sudo snap install dive sudo snap connect dive:docker-executables docker:docker-executables sudo snap connect dive:docker-daemon docker:docker-daemonThis is the cause of the common "no images found after
snap install dive" problem. If you use Docker fromapt, prefer the.debpackage instead.
Note
Installing with
go installworks, but the binary will not report a proper version withdive --version(the version is injected at release build time, not bygo install).
Run as a Docker container (no install)
You can run dive itself as a container. Because it talks to your Docker engine, you must mount the Docker socket:
docker run --rm -it \
-v /var/run/docker.sock:/var/run/docker.sock \
docker.io/wagoodman/dive:latest <dive arguments...>
A convenient shell alias:
alias dive="docker run -ti --rm -v /var/run/docker.sock:/var/run/docker.sock docker.io/wagoodman/dive"
dive nginx:latest
The image is published to both registries:
docker pull docker.io/wagoodman/dive
# or
docker pull ghcr.io/wagoodman/dive
Tip
Depending on your local Docker version you may need to pin the API version so the containerized
divecan talk to your daemon:docker run --rm -it \ -v /var/run/docker.sock:/var/run/docker.sock \ -e DOCKER_API_VERSION=1.37 \ docker.io/wagoodman/dive:latest <dive arguments...>
Verify the install
dive --version
dive 0.x.x
3. Requirements
| Requirement | Why you need it | Notes |
|---|---|---|
| A container engine — Docker or Podman | dive fetches and (for build) builds images through an engine |
Docker is the default. Podman is supported on Linux and macOS only. |
| Access to the Docker socket | The default docker source reads images from the running daemon |
Typically /var/run/docker.sock. Containerized dive must mount it. |
The docker (or podman) client binary on your PATH |
dive build shells out to the real docker build / podman build |
If missing, you get cannot find docker client executable. |
| A terminal that supports a full-screen TUI | Interactive mode draws panes, colors, and borders | Not needed in CI mode (--ci / CI=true). |
Note
If you only have an image tar archive on disk (for example exported by
docker saveor produced by a build tool like Kaniko), you do not need a running engine at all — use thedocker-archivesource described in Choosing where the image comes from.
4. Everyday Usage
4.1 Analyzing an image
The most common command is simply the image reference:
dive <image>
<image> can be a tag, an ID, or a digest:
dive nginx:latest
dive 1f2c1c8a0e3b
dive nginx@sha256:ad9fa4d07136a83e69a54ef00102f579d04eba431932de3b0f098cc5d5948f9f
4.2 Choosing where the image comes from
By default dive reads from the Docker engine. To pull from a different source, either pass --source or prefix the image with a source:// scheme. The two forms are equivalent:
dive <image> --source <source>
# or
dive <source>://<image>
| Source | Meaning | Availability |
|---|---|---|
docker |
Read from the Docker engine (the default) | All platforms |
docker-archive |
Read a Docker/OCI tar archive from disk | All platforms |
podman |
Read from the Podman engine | Linux and macOS only |
Examples:
# from the Docker engine (default — both are the same)
dive nginx:latest
dive docker://nginx:latest
# from a tar archive on disk
dive --source docker-archive ./my-image.tar
dive docker-archive://./my-image.tar
# from Podman
dive --source podman localhost/my-app:dev
dive podman://localhost/my-app:dev
Note
docker-taris accepted as an alias fordocker-archive.
Warning
If you pass an invalid value to
--source,divewarns and falls back todocker:invalid container engine: <value> (valid options: docker, podman, docker-archive), using default "docker".
4.3 Building and analyzing together
dive build [any valid "docker build" arguments]
dive build runs your build through the engine and then analyzes the result. Everything after build is passed verbatim to docker build:
dive build -t my-app:latest .
dive build -t my-app:latest -f Dockerfile.prod --build-arg ENV=prod .
Important
Because
buildforwards all arguments straight todocker build,dive-specific flags do not work afterbuild. Configuredive's behavior for build runs through a config file or environment variables (for exampleCI=true dive build -t my-app .), not through flags like--sourceplaced afterbuild.
On macOS (Docker engine only), mount your working directory and config when running the container form:
docker run --rm -it \
-v /var/run/docker.sock:/var/run/docker.sock \
-v "$(pwd)":"$(pwd)" \
-w "$(pwd)" \
-v "$HOME/.dive.yaml":"$HOME/.dive.yaml" \
docker.io/wagoodman/dive:latest build -t my-app:latest .
4.4 Continuing past parse errors
If an image archive has entries dive cannot parse, the run normally stops. To analyze what it can read and keep going, use --ignore-errors:
dive my-image:latest --ignore-errors
# short form
dive my-image:latest -i
4.5 Exporting the analysis as JSON
To capture the analysis as a machine-readable file (and skip the UI entirely), use --json:
dive my-app:latest --json analysis.json
# short form
dive my-app:latest -j analysis.json
The file contains per-layer file lists plus image-level totals — overall size, wasted bytes, efficiency score, and the list of inefficient files:
{
"layer": [
{
"index": 0,
"id": "...",
"digestId": "sha256:...",
"sizeBytes": 5560320,
"command": "FROM ...",
"fileList": [ /* per-file metadata */ ]
}
],
"image": {
"sizeBytes": 14830080,
"inefficientBytes": 1485633,
"efficiencyScore": 0.9845,
"fileReference": [
{ "count": 2, "sizeBytes": 1048576, "file": "/var/cache/app/data" }
]
}
}
Warning
The directory you write to must already exist, or
divestops withdirectory for JSON export does not exist: <dir>.
5. Reading the Interactive UI
The screen is split into a layer pane (left) and a file tree pane (right), with layer/image details and a status bar.
- Layer pane (left). Each row is one image layer, showing its size and the command (
Dockerfileinstruction) that created it. Select a layer to see its contents on the right. - File tree pane (right). Shows the filesystem as of the selected layer, combined with all layers below it. Files are color-coded by how they changed in the selected layer.
- Details panes. Below the layer list you get layer details, image details, and the efficiency report (efficiency score and estimated wasted space).
What the colors mean
| Color | Change type | Meaning |
|---|---|---|
| Green | Added | The file is new in this layer |
| Yellow | Modified | The file's contents or attributes changed in this layer |
| Red | Removed | The file was deleted in this layer |
| Default | Unmodified | The file is unchanged from a lower layer |
Tip
Use Ctrl+A in the layer view to see aggregated changes (everything modified from the first layer up to the selected one), and Ctrl+L to see only the current layer's changes.
6. Keyboard Shortcuts
These are the default bindings. Every one of them can be remapped in a config file — see Customizing keybindings.
| Key | Action |
|---|---|
| Ctrl+C or Q | Quit |
| Tab | Switch between the layer and file tree views |
| Ctrl+F | Filter files by name |
| Esc | Close the file filter |
| ↑ or K | Move up one line |
| ↓ or J | Move down one line |
| ← or H | Move to the previous pane / left |
| → or L | Move to the next pane / right |
| PageUp or U | Scroll up a page |
| PageDown or D | Scroll down a page |
Layer view only
| Key | Action |
|---|---|
| Ctrl+A | Show aggregated image modifications (all layers up to this one) |
| Ctrl+L | Show only the current layer's modifications |
File tree view only
| Key | Action |
|---|---|
| Space | Collapse / uncollapse the selected directory |
| Ctrl+Space | Collapse / uncollapse all directories |
| Ctrl+A | Show / hide added files |
| Ctrl+R | Show / hide removed files |
| Ctrl+M | Show / hide modified files |
| Ctrl+U | Show / hide unmodified files |
| Ctrl+B | Show / hide file attributes (permissions, owner, size) |
Note
A few additional actions exist as configurable bindings even though they have no default key assigned in the shipped table: toggling the file sort order, toggling tree wrapping, and extracting a file's contents. You can bind keys to them in the
keybinding:section of your config (see below). Some bindings — like the file filter — accept multiple keys; the first key listed is the one shown in the UI hint.
7. Configuration
dive works with zero configuration. A config file is only needed when you want to change defaults — display preferences, CI rules, or keybindings.
7.1 Where config files live
dive searches these locations, in order, and accepts .yaml or .yml:
$XDG_CONFIG_HOME/dive/*.yaml$XDG_CONFIG_DIRS/dive/*.yaml~/.config/dive/*.yaml~/.dive.yaml
You can also point at a specific file two ways:
# explicit config flag
dive nginx:latest -c ./my-dive.yaml
# or via environment variable
DIVE_CONFIG=./my-dive.yaml dive nginx:latest
To see the configuration dive actually resolved (defaults merged with your file), run:
dive config
7.2 Display preferences
A complete UI config file, with the built-in defaults shown:
# which engine to read images from: "docker" or "podman"
container-engine: docker
# continue analysis even if the image archive has parse errors
ignore-errors: false
log:
enabled: true
path: ./dive.log
level: info
diff:
# file change types to hide in the tree by default.
# valid values: added, removed, modified, unmodified
hide:
- added
- removed
filetree:
# start with directories collapsed
collapse-dir: false
# fraction of screen width for the file tree pane (must be >0 and <1)
pane-width: 0.5
# show file attributes (permissions/owner/size) next to the tree
show-attributes: true
layer:
# show changes aggregated across all previous layers by default
show-aggregated-changes: false
| Key | Default | Meaning |
|---|---|---|
container-engine |
docker |
Engine to read images from (docker or podman) |
ignore-errors |
false |
Keep analyzing past archive parse errors |
diff.hide |
(none — all shown) | Change types hidden in the tree by default |
filetree.collapse-dir |
false |
Start with directories collapsed |
filetree.pane-width |
0.5 |
File tree pane width as a fraction of the screen |
filetree.show-attributes |
true |
Show permission/owner/size columns |
layer.show-aggregated-changes |
false |
Default to aggregated vs. single-layer changes |
Warning
filetree.pane-widthmust be strictly between0and1. If you set it to0,1, or outside that range,divewarns and resets it to0.5.
Note
diff.hidevalues are validated. An unrecognized entry produces a warning likeinvalid diff hide value: <value> (valid values: added, removed, modified, unmodified)and is ignored.
7.3 Customizing keybindings
Every action's keys live under keybinding:. Separate multiple keys with a comma; the first key is the one shown in the UI hint.
keybinding:
# Global
quit: ctrl+c
toggle-view: tab
filter-files: ctrl+f, ctrl+slash
close-filter-files: esc
up: up,k
down: down,j
left: left,h
right: right,l
# Layer view
compare-all: ctrl+a
compare-layer: ctrl+l
# File tree view
toggle-collapse-dir: space
toggle-collapse-all-dir: ctrl+space
toggle-added-files: ctrl+a
toggle-removed-files: ctrl+r
toggle-modified-files: ctrl+m
toggle-unmodified-files: ctrl+u
toggle-filetree-attributes: ctrl+b
page-up: pgup,u
page-down: pgdn,d
8. Using dive in CI
CI mode turns dive into a gate: it analyzes the image, checks it against efficiency rules, and exits non-zero if a rule fails — no interactive UI.
8.1 Turning on CI mode
Either set the environment variable or pass the flag:
CI=true dive my-app:latest
# or
dive my-app:latest --ci
CI=true (also 1 or yes) enables it from the environment; --ci enables it explicitly.
8.2 The three rules
CI evaluates up to three rules. Each can be a number or the literal disabled (also off/false/empty) to skip it.
| Rule | Fails when | Built-in default |
|---|---|---|
lowest-efficiency |
Efficiency drops below this ratio (0–1) | 0.9 |
highest-wasted-bytes |
Wasted space is at or above this size (e.g. 20MB, 100kB) |
disabled |
highest-user-wasted-percent |
Wasted space is at or above this fraction (0–1) of user-added bytes | 0.1 |
Note
These are the defaults baked into
divewhen no.dive-cifile is present. A.dive-cifile you write replaces them with whatever you specify.
8.3 The .dive-ci rules file
Put a .dive-ci file at the root of your repo (this is the default path; override it with --ci-config):
rules:
# fail if efficiency is below 95%
lowest-efficiency: 0.95
# fail if wasted space is 20 MB or more
highest-wasted-bytes: 20MB
# fail if 20% or more of user-added bytes are wasted
highest-user-wasted-percent: 0.20
dive my-app:latest --ci --ci-config ./.dive-ci
Note
Older configs used camelCase keys (
lowestEfficiency,highestWastedBytes,highestUserWastedPercent). These still load, butdivewarns that they are deprecated — prefer the snake-case names above.
Tip
Wasted-space sizes accept human units:
B,kB,MB,GB(e.g.highest-wasted-bytes: 20MB).
8.4 In a Docker-based pipeline
docker run --rm -t \
--env CLICOLOR_FORCE=true \
-v /var/run/docker.sock:/var/run/docker.sock \
docker.io/wagoodman/dive:latest \
my-app:latest \
--ci
Important
CI runs still need access to the image. Mount the Docker socket (as above), or analyze a tar archive with
--source docker-archiveso no engine is required.
9. Common Tasks
Find the biggest space wasters in an image
dive my-app:latest
Read the efficiency report pane. Then in the file tree, press Ctrl+U to hide unmodified files and Ctrl+A in the layer view to see aggregated changes — duplicated/removed files stand out.
Gate a build in CI on image efficiency
# .dive-ci at repo root, then:
CI=true dive my-app:latest
Analyze an image that is not in your engine (only a tar on disk)
docker save my-app:latest -o my-app.tar
dive --source docker-archive my-app.tar
Capture results for later or for another tool
dive my-app:latest --json analysis.json
Hide the noise and focus on what each layer added
# ~/.dive.yaml
diff:
hide:
- unmodified
10. Troubleshooting
Error: cannot find docker client executable
Cause. dive build (or the Docker source) cannot find the docker binary on your PATH.
Solution. Install the Docker client and ensure it is on PATH. In the containerized dive, make sure you are using an image/tag that bundles the client, and mount the socket.
Prevention. Run docker version first — if that fails, dive will too.
Error: could not find image config
Cause. The image archive is missing its config blob, or is in a format dive could not fully parse (reported in issue #452). Often seen with unusual builders or partial archives.
Solution. Re-export the image (docker save), or pull a fresh copy. If you only need a partial analysis, add --ignore-errors.
Prevention. Analyze images produced by a standard docker build/docker save or a current OCI exporter.
Error: could not find Containerfile or Dockerfile
Cause. dive build was pointed at a directory that has no Dockerfile or Containerfile.
Solution. Run dive build from a directory containing a build file, or pass -f <path> to point at one.
Prevention. Confirm the build context is correct, exactly as you would for docker build.
Error: unable to determine image resolver
Cause. The source could not be resolved — usually an invalid --source value or an unrecognized scheme:// prefix.
Solution. Use one of docker, docker-archive, or podman. Check the source://image spelling.
Prevention. Stick to the documented sources in section 4.2.
Error: cannot find podman client executable
Cause. You selected the podman source but Podman is not installed or not on PATH.
Solution. Install Podman (Linux/macOS only) or switch to the Docker source.
Prevention. dive does not support Podman on Windows.
No images appear after installing via Snap
Cause. The Snap package needs its Docker interfaces connected, and conflicts with a Docker installed via apt (issue #546).
Solution. Connect the interfaces (snap connect dive:docker-executables docker:docker-executables and dive:docker-daemon docker:docker-daemon), or uninstall the Snap and use the .deb package.
Prevention. If Docker came from apt, do not use the Snap build of dive.
The wrong Docker host is used on Windows
Cause. On Windows / alternative runtimes the default Docker host can be detected incorrectly (issue #649).
Solution. Set DOCKER_HOST explicitly. With Colima or similar:
export DOCKER_HOST=$(docker context inspect -f '{{ .Endpoints.docker.Host }}')
Prevention. Export DOCKER_HOST in your shell profile when using a non-standard runtime.
dive hangs right after the main view opens
Cause. A known race condition when starting in some terminals/containers (issue #513).
Solution. Resize the terminal or relaunch; ensure your terminal is a real TTY (for the container form, pass -it).
Prevention. Run interactive mode in a proper terminal; use CI mode in non-interactive environments.
The containerized dive cannot reach the daemon
Cause. Docker API version mismatch between the container and your host daemon. Solution. Pin the API version:
DOCKER_API_VERSION=1.37 dive ...
# or with the container form, add: -e DOCKER_API_VERSION=1.37
Prevention. Set DOCKER_API_VERSION when host and client versions differ.
dive --version shows no real version
Cause. You installed via go install, which does not inject build-time version info.
Solution. Install a released binary (package manager or releases page) if you need an accurate version string.
Prevention. Use packaged releases for production use.
11. FAQ
Q: Does dive modify or rebuild my image?
No. It reads the image (from an engine or a tar) and analyzes it in memory. The only command that creates anything is dive build, which runs your docker build and then analyzes the result.
Q: What exactly is the "efficiency" score?
It is an estimate of how much of your image's bytes are "necessary." Roughly: for every file path, dive compares the smallest version of that file it ever saw against the total bytes that path consumed across all layers. Files that are duplicated, moved, or deleted across layers drag the score down. A score near 100% means almost no wasted space.
Q: What counts as "wasted" space? Bytes spent on files that appear in more than one layer, or that are added and then removed in a later layer. The base image layer is excluded from the "user wasted percent" calculation.
Q: Can I use dive without Docker?
Yes — analyze a tar archive with --source docker-archive ./image.tar, which needs no running engine. You can also use Podman on Linux/macOS.
Q: Does dive work with Podman on Windows?
No. Podman support is Linux and macOS only. Use the Docker source on Windows.
Q: How do I run dive headless in a pipeline?
Set CI=true (or pass --ci). This skips the UI and returns a pass/fail exit code based on your .dive-ci rules.
Q: My --source docker flag after build is ignored. Why?
dive build forwards everything after build to docker build, so dive flags placed there are passed to Docker, not dive. Configure build-run behavior via a config file or environment variables instead.
Q: Can I change the keyboard shortcuts?
Yes. Remap any action under the keybinding: section of your config file. Multiple keys per action are allowed (comma-separated); the first is shown in the UI hint.
Q: How do I make dive keep going when an image has unreadable entries?
Add --ignore-errors (-i). It analyzes what it can and skips the rest.
Q: Where should I put the CI rules file?
A .dive-ci file at the repo root is picked up automatically. Use --ci-config <path> to point elsewhere.
Q: Can I get the analysis as data instead of a UI?
Yes — dive <image> --json <file> writes per-layer file lists and image-level totals (size, wasted bytes, efficiency, inefficient files) as JSON.
12. Command & Flag Reference
Commands
| Command | Purpose |
|---|---|
dive [IMAGE] |
Fetch and analyze an image; open the UI (or run CI checks) |
dive build [docker build args] |
Build an image via docker build, then analyze it |
dive version |
Print version information |
dive config |
Print the resolved configuration |
Flags (root and build)
| Flag | Short | Default | Purpose |
|---|---|---|---|
--source <engine> |
docker |
Where to read the image from: docker, docker-archive, podman |
|
--ignore-errors |
-i |
false |
Continue analysis past image parse errors |
--ci |
false |
Skip the UI and validate against CI rules (same as CI=true) |
|
--ci-config <path> |
.dive-ci |
Rules file to use in CI mode | |
--json <file> |
-j |
Write the layer analysis to a JSON file and skip the UI | |
--lowestEfficiency <ratio> |
(only with --ci) override the lowest-efficiency rule |
||
--highestWastedBytes <size> |
(only with --ci) override the highest-wasted-bytes rule |
||
--highestUserWastedPercent <ratio> |
(only with --ci) override the highest-user-wasted-percent rule |
||
--config <path> |
-c |
Use a specific config file | |
--help |
-h |
Show help |
Note
--helpon the root command also prints the full resolvable application configuration, so you can see every setting and its current value at a glance.
Environment variables
| Variable | Effect |
|---|---|
CI |
true/1/yes enables CI mode |
DIVE_CONFIG |
Path to a config file to load |
DOCKER_HOST |
Docker daemon endpoint (useful with Colima/Rancher/Podman-machine) |
DOCKER_API_VERSION |
Pin the Docker API version for client/daemon compatibility |
DOCKER_TLS_VERIFY |
Enable TLS verification against the Docker daemon |
DOCKER_CONTEXT |
Select a Docker context |
CLICOLOR_FORCE |
true forces colored output (handy inside CI containers) |