Skip to content
Go back

Lazydocker & Dive: Fix Your Docker CLI

By SumGuy 11 min read
Lazydocker & Dive: Fix Your Docker CLI

Docker’s CLI Is Technically Fine (and That’s the Problem)

Let’s get something out of the way: docker ps, docker logs, docker stats — they all work. They’re functional. They get the job done in the same way that a spoon technically works as a screwdriver. Nobody’s going to stop you, but you’re making life harder than it needs to be.

The Docker CLI was designed by people who think in terms of composable Unix commands and pipe chains. And that’s great if you’re writing shell scripts. But if you’re a human being who just wants to see what’s running, check some logs, and maybe restart a container without typing four different commands, you deserve better tools.

Enter lazydocker and dive — two CLI tools that approach Docker from completely different angles but share the same core philosophy: you shouldn’t need a photographic memory of Docker’s 50+ subcommands to be productive.

Lazydocker gives you a full terminal UI for managing containers, images, volumes, and networks. Dive lets you crack open Docker images and inspect every layer to figure out why your “simple” Node app is somehow 1.2 GB. Together, they cover the two biggest pain points in daily Docker work: management and optimization.

Let’s dig in.

Lazydocker: A Full Docker Dashboard in Your Terminal

Lazydocker is from Jesse Duffield, the same person who made lazygit (which should tell you something about his feelings toward typing long commands). It’s a terminal UI that gives you a real-time dashboard of everything Docker is doing on your system.

Think of it as docker ps + docker logs + docker stats + docker compose all fused together in a single interactive interface that you navigate with your keyboard. It’s what Docker Desktop wishes it was, minus the Electron memory footprint and the questionable licensing decisions.

Installing Lazydocker

You’ve got options. Pick your poison.

Homebrew (macOS and Linux):

Terminal window
brew install lazydocker

Go install (if you have Go 1.21+):

Terminal window
go install github.com/jesseduffield/lazydocker@latest

Binary release (any Linux distro):

Terminal window
curl https://raw.githubusercontent.com/jesseduffield/lazydocker/master/scripts/install_update_linux.sh | bash

Docker (because of course):

Terminal window
docker run --rm -it \
-v /var/run/docker.sock:/var/run/docker.sock \
-v ~/.config/lazydocker:/.config/jesseduffield/lazydocker \
lazyteam/lazydocker

Yes, you can run a Docker management tool inside Docker. It’s turtles all the way down.

Nix:

Terminal window
nix-env -iA nixpkgs.lazydocker

Once it’s installed, just type lazydocker in any terminal and you’re in.

The Interface: What You’re Looking At

When lazydocker launches, you’ll see a multi-panel layout that might feel familiar if you’ve ever used tmux or any tiling window manager. Here’s the breakdown:

The whole thing updates in real time. Container CPU and memory usage are graphed live. Logs stream in as they happen. It’s like having watch docker stats and docker logs -f running simultaneously, except you don’t need six terminal tabs to do it.

Essential Keybindings

Here’s where lazydocker really shines. Instead of remembering docker restart my-container-name, you navigate to the container and press a key:

KeyAction
enterFocus on selected item
dRemove container/image/volume
sStop container
rRestart container
aAttach to container
mView logs
eOpen shell in container (exec)
EExec with custom command
bView bulk commands
cRun custom command
[ / ]Switch between panels
xOpen context menu
/Filter

The x key is the big one. It opens a context menu with every action available for whatever you’ve selected. If you forget everything else, remember x.

For containers specifically, you can view logs with timestamps, restart/stop/start/pause/remove, attach a shell session, inspect the full container config, and see real-time resource usage.

For images: remove individual or dangling images, see which containers are using an image, view image layer history.

Customizing Lazydocker

The config file lives at ~/.config/lazydocker/config.yml. Here are some tweaks worth making:

~/.config/lazydocker/config.yml
gui:
scrollHeight: 2
language: 'auto'
theme:
activeBorderColor:
- green
- bold
inactiveBorderColor:
- white
selectedLineBgColor:
- blue
reporting: "off"
commandTemplates:
dockerCompose: "docker compose"
restartService: "docker compose restart {{ .Service.Name }}"
logs:
timestamps: true
since: "60m"
tail: "200"

A few things worth noting:

Custom Commands: The Secret Weapon

You can define custom commands per resource type in the config:

~/.config/lazydocker/config.yml
customCommands:
containers:
- name: "View container IP"
attach: false
command: "docker inspect --format '{{ .NetworkSettings.IPAddress }}' {{ .Container.ID }}"
- name: "Export logs to file"
attach: false
command: "docker logs {{ .Container.ID }} > /tmp/{{ .Container.Name }}_logs.txt 2>&1"
images:
- name: "Dive into image"
attach: true
command: "dive {{ .Image.ID }}"

That last one — select an image in lazydocker and jump straight into dive for layer analysis. Chef’s kiss.

Dive: X-Ray Vision for Docker Images

Dive is a tool for exploring Docker images, their layer contents, and figuring out where all that disk space went. If you’ve ever stared at a 900 MB image and wondered “but my app is only 15 lines of Python,” dive is about to become your best friend.

Docker images are made of layers, and each layer adds files. But you can’t see what’s in those layers easily. docker history gives you a vague summary. docker inspect gives you JSON that requires an advanced degree to parse. Dive gives you an actual file browser where you can walk through each layer and see exactly what was added, modified, or removed.

Installing Dive

Homebrew:

Terminal window
brew install dive

Go install:

Terminal window
go install github.com/wagoodman/dive@latest

Debian/Ubuntu (.deb):

Terminal window
DIVE_VERSION=$(curl -sL "https://api.github.com/repos/wagoodman/dive/releases/latest" | grep '"tag_name"' | sed -E 's/.*"v([^"]+)".*/\1/')
curl -OL "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"

Docker:

Terminal window
docker run --rm -it \
-v /var/run/docker.sock:/var/run/docker.sock \
wagoodman/dive:latest <your-image>

Using Dive: The Basics

Point dive at any image:

Terminal window
dive nginx:latest

Build and analyze in one step:

Terminal window
dive build -t my-app:latest .

This runs docker build and immediately opens the result in dive. No extra steps.

The Dive Interface

Dive gives you a two-panel layout:

Left panel: Layers

Right panel: File tree

Key Dive Keybindings

KeyAction
TabSwitch between layers and file tree
Ctrl+AToggle showing added files
Ctrl+RToggle showing removed files
Ctrl+MToggle showing modified files
Ctrl+UToggle showing unmodified files
Ctrl+FFilter files
SpaceCollapse/expand directory

The filter feature (Ctrl+F) is particularly useful. Looking for that mystery .cache directory eating 400 MB? Filter for it.

Practical Dive Walkthrough: Why Is This Image So Big?

Let’s walk through a real scenario. You have this Dockerfile:

Dockerfile
FROM node:20
WORKDIR /app
COPY package*.json ./
RUN npm install
COPY . .
RUN npm run build
EXPOSE 3000
CMD ["node", "dist/index.js"]

You build it and the image is 1.3 GB. Your app is 200 lines of TypeScript. Something has gone horribly wrong.

Run dive build -t my-app:debug . and here’s what you’ll likely find:

  1. Layer 1 (FROM node:20) — ~350 MB. That’s the full Node.js runtime on Debian. Already a chonker.
  2. Layer 3 (npm install) — ~450 MB. Your node_modules directory in all its glory.
  3. Layer 4 (COPY .) — ~200 MB. Wait, that copied node_modules again? And your .git directory? And that 150 MB test fixture you forgot about?
  4. Layer 5 (npm run build) — ~50 MB. The actual build output.

Dive makes all of this obvious at a glance. Armed with that insight, you rewrite:

Dockerfile
FROM node:20-alpine AS builder
WORKDIR /app
COPY package*.json ./
RUN npm ci --only=production
COPY . .
RUN npm run build
FROM node:20-alpine
WORKDIR /app
COPY --from=builder /app/dist ./dist
COPY --from=builder /app/node_modules ./node_modules
COPY --from=builder /app/package.json ./
EXPOSE 3000
CMD ["node", "dist/index.js"]

Add a .dockerignore:

.dockerignore
.git
node_modules
*.md
tests/
.env
.vscode

Run dive again on the new build. Instead of 1.3 GB, you’re looking at ~180 MB.

Dive’s Image Efficiency Score

At the bottom of the dive UI, you’ll see an efficiency score — how much wasted space exists in your image (files added in one layer and removed in another, but still taking up space because layers).

A score of 100% means no wasted space. Anything below 95% is worth investigating. Below 90%? You’ve got problems.

Common efficiency killers: running apt-get install and apt-get clean in separate RUN commands (cleanup in a new layer doesn’t save space), copying files then deleting them in another layer, npm install with dev dependencies then removing them later.

The fix is almost always to combine operations in a single RUN:

RUN apt-get update && \
apt-get install -y --no-install-recommends some-package && \
rm -rf /var/lib/apt/lists/*

CI Integration: Automated Image Checks with Dive

Dive isn’t just an interactive tool. Run it in CI to automatically fail builds when images are too bloated.

Dive CI Configuration

Create .dive-ci.yml in your project root:

.dive-ci.yml
rules:
lowestEfficiency: 0.9
highestWastedBytes: 50mb
highestUserWastedPercent: 0.15

GitHub Actions Example

.github/workflows/docker-check.yml
name: Docker Image Check
on:
push:
paths:
- 'Dockerfile'
- '.dockerignore'
- 'package*.json'
jobs:
analyze:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Build image
run: docker build -t my-app:ci .
- name: Install dive
run: |
DIVE_VERSION=$(curl -sL "https://api.github.com/repos/wagoodman/dive/releases/latest" | grep '"tag_name"' | sed -E 's/.*"v([^"]+)".*/\1/')
curl -OL "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"
- name: Analyze image
run: CI=true dive my-app:ci --ci-config .dive-ci.yml

Now every PR that touches the Dockerfile gets automatically checked for image bloat. No more “we’ll optimize it later” that never actually happens.

Bonus: DockerSlim (now just Slim)

If lazydocker is your dashboard and dive is your x-ray machine, Slim (formerly DockerSlim) is the surgeon. It automatically analyzes and optimizes your Docker images by removing everything that isn’t needed at runtime.

It launches your container in a sandboxed environment, monitors which files are actually accessed, then builds a minimal image containing only those files. A 300 MB image might slim down to 30 MB.

Terminal window
# Install
curl -sL https://raw.githubusercontent.com/slimtoolkit/slim/master/scripts/install-slim.sh | sudo -E bash -
# Basic usage
slim build --target my-app:latest --tag my-app:slim
# With HTTP probing for web apps
slim build \
--target my-app:latest \
--tag my-app:slim \
--http-probe-cmd /health \
--expose 3000

A word of caution: slim can break things if your app accesses files dynamically or at paths not exercised during probing. Timezone data, SSL certificates, and locale files are common casualties. Always test your slimmed images thoroughly.

The Complete Docker Toolbelt

TaskToolWhy
Monitor running containerslazydockerReal-time TUI dashboard
Debug container issueslazydockerQuick log access, shell exec
Analyze image sizediveLayer-by-layer file browser
CI image quality gatesdive (CI mode)Automated efficiency checks
Aggressive image optimizationslimAuto-minification

None of these tools replace understanding Docker fundamentals. You still need to know how layers work, why multi-stage builds matter, what a .dockerignore does. But they make the gap between “knowing the concepts” and “actually applying them efficiently” a lot smaller.

Quick Start: Get Running in 5 Minutes

Terminal window
# Install both tools
brew install lazydocker dive
# Start your Docker environment
docker compose up -d
# Open lazydocker to see everything
lazydocker
# In another terminal, analyze an image
dive nginx:latest

Spend 10 minutes clicking around lazydocker. Navigate between containers, check logs, look at stats. Then switch to dive and explore a few images. Once you’ve used both tools for a day, the raw Docker CLI will feel like going back to dial-up internet.

Install them. Use them. Your future self — the one who won’t have to type docker logs --tail 100 -f that-container-with-the-really-long-name ever again — will thank you.


Share this post on:

Send a Webmention

Written about this post on your own site? Send a webmention and it may appear here.


Previous Post
Docker Networking Demystified
Next Post
Using AI to Find Security Bugs in Your Code

Related Posts