Dive Tool Architecture: How the Container Image Analyzer Is Structured
The Dive tool architecture implements a modular, event-driven Go application that separates CLI command dispatch, image resolution, domain modeling, analysis computation, and terminal UI rendering into distinct, loosely-coupled layers.
The Dive container image analyzer from wagoodman/dive is architected as a layered Go program designed to inspect Docker and OCI images efficiently. This architecture cleanly isolates container engine interactions from filesystem analysis and user interface concerns, enabling support for multiple backends while maintaining a responsive terminal experience through asynchronous event handling.
Architectural Layers and Component Responsibilities
The codebase organizes functionality into distinct packages, each with well-defined responsibilities and minimal cross-dependencies.
CLI and Command Dispatcher
The entry point and command routing layer handles flag parsing and application initialization. In cmd/dive/main.go, the application constructs a clio.Application instance and delegates to the root command defined in cmd/dive/cli/internal/command/root.go. This layer interprets sub-commands such as build, export, and ci, routing them to appropriate handlers while constructing the image resolver pipeline.
Image Resolution Abstraction
Before analysis begins, the system must determine how to access the target image. The dive/get_image_resolver.go file implements GetImageResolver, which detects the image source—whether Docker engine, Podman engine, or Docker archive—and returns a concrete implementation of the image.Resolver interface. This pluggable design allows new container backends to be added without modifying core analysis logic.
Core Domain Models
The heart of the architecture resides in the dive/image and dive/filetree packages. The dive/image/image.go file defines the Image struct, which aggregates a slice of Layer objects defined in dive/image/layer.go. Each Layer maintains its own filetree.FileTree instance (from dive/filetree/file_tree.go), representing the complete filesystem snapshot at that layer's state. This layer-centric modeling mirrors the container image format directly and simplifies differential calculations.
Analysis Engine
Once image data is loaded, dive/image/analysis.go executes the analysis phase. This engine walks every layer's FileTree to compute metrics including added files, removed files, modifications, and efficiency scores. The results are stored back into the Image struct, creating a comprehensive audit trail of space usage and duplication across layers.
Event-Driven UI Architecture
The terminal interface lives under cmd/dive/cli/internal/ui/v1 and follows an event-driven pattern. The internal/bus/bus.go implementation provides a publish-subscribe mechanism where analysis progress and user interactions emit events (such as event.LayerSelected). UI components in cmd/dive/cli/internal/ui/v1/app/app.go and view files like cmd/dive/cli/internal/ui/v1/view/layer.go subscribe to these events, enabling asynchronous updates without blocking the main analysis goroutine.
Command Adapters and Utilities
Bridging the CLI and domain layers, the cmd/dive/cli/internal/command/adapter/resolver.go file contains adapters that translate high-level CLI actions into concrete operations. These adapters orchestrate the resolution, analysis, and export phases, ensuring that non-interactive commands (like CI evaluation or JSON export) can reuse the same core logic as the interactive TUI. Supporting infrastructure in internal/utils/format.go and internal/log/log.go provides formatting helpers and structured logging across all layers.
Execution Flow Through the System
A typical analysis session follows a precise pipeline through these architectural layers:
-
Application Startup –
cmd/dive/main.goinstantiates theclio.Applicationand invokesapp.Run(), triggering the root command setup. -
Source Detection – The root command calls
dive.DeriveImageSource()anddive.GetImageResolver()to instantiate the appropriate resolver for the image source (Docker daemon, Podman socket, or tar archive). -
Image Hydration – The resolver fetches the image manifest and constructs
image.Layerobjects, each initialized with afiletree.FileTreerepresenting that layer's filesystem contribution. -
Statistical Analysis – The
image.Analysisengine traverses everyFileTree, calculating per-layer statistics including cumulative size, file additions, deletions, and efficiency ratios. -
Event Publication – Throughout analysis, the system posts progress events to
internal/bus/event, allowing the UI to display real-time status updates without interrupting computation. -
Interactive Rendering – The UI package (
cmd/dive/cli/internal/ui/v1) renders the layer list, file tree view, and diff view. User interactions generate bus events that trigger selective re-rendering of affected components. -
Output Generation – For export or CI operations, command adapters consume the analyzed
Imagemodel and serialize results to JSON, CSV, or evaluation reports.
Key Design Decisions
Several architectural choices distinguish Dive's implementation:
-
Separation of Concerns – Core data structures in
dive/imageanddive/filetreecontain no UI dependencies, ensuring that headless analysis commands (e.g.,dive --json) execute without importing terminal rendering libraries. -
Pluggable Resolver Interface – The
image.Resolverabstraction indive/get_image_resolver.godecouples the analysis engine from specific container runtimes, making it straightforward to add support for OCI registries or alternative container engines. -
Event-Driven Decoupling – By routing all state changes through the internal bus (
internal/bus/bus.go), long-running analysis operations remain non-blocking, while the UI maintains responsiveness through asynchronous event consumption. -
Layer-Centric Domain Model – Representing each container layer as a discrete
Layerwith its ownFileTree(rather than global filesystem state) directly mirrors the underlying image specification and enables efficient differential analysis between arbitrary layer indices.
Working with the Architecture
Developers extending Dive interact with these key APIs.
Creating and using an image resolver:
src, name := dive.DeriveImageSource("docker://alpine:latest")
resolver, err := dive.GetImageResolver(src)
if err != nil {
// handle resolution error
}
img, err := resolver.Resolve(name) // returns *image.Image
Inspecting layer metadata:
for i, layer := range img.Layers {
fmt.Printf("%d – %s – %s\n", i, layer.ShortId(),
humanize.Bytes(layer.Size))
}
Subscribing to UI events:
bus.Subscribe(event.LayerSelected, func(e event.Event) {
// redraw file tree for the selected layer
// e.Data contains the selected layer reference
})
Summary
- Dive tool architecture implements a strict separation between CLI dispatch, image resolution, domain models, analysis computation, and UI rendering.
- The Image Resolver pattern in
dive/get_image_resolver.goabstracts multiple container engines behind a common interface. - FileTree structures in
dive/filetree/file_tree.gorepresent per-layer filesystem states, enabling efficient differential analysis. - An event bus in
internal/bus/bus.godecouples long-running analysis from the terminal UI, maintaining application responsiveness. - Command adapters translate CLI inputs into domain operations, supporting both interactive TUI and automated CI/export workflows.
Frequently Asked Questions
What role does the Image Resolver play in Dive's architecture?
The Image Resolver acts as a factory and strategy pattern implementation that detects the image source type (Docker daemon, Podman socket, or Docker archive) and returns a concrete image.Resolver capable of fetching that specific image format. Located in dive/get_image_resolver.go, this component isolates engine-specific logic from the core analysis engine, allowing Dive to support multiple container runtimes through a unified interface.
How does Dive maintain UI responsiveness during long-running analysis?
Dive employs an event-driven architecture centered on the internal bus defined in internal/bus/bus.go. The analysis engine publishes progress events asynchronously while running in separate goroutines, and the UI components subscribe to these events to update views. This decoupling prevents the terminal interface from blocking while filesystem trees are being parsed and compared across layers.
What data structures enable Dive's layer differential analysis?
The architecture uses a layer-centric model where each container layer is represented by a Layer struct (in dive/image/layer.go) containing its own filetree.FileTree instance. The FileTree implementation in dive/filetree/file_tree.go provides tree traversal and diff capabilities, allowing the analysis engine in dive/image/analysis.go to compute exactly which files were added, removed, or modified between any two layer indices without re-processing the entire image.
How does the command adapter pattern support different output modes?
The command adapters in cmd/dive/cli/internal/command/adapter/resolver.go translate generic CLI requests into concrete domain operations. By sitting between the CLI layer and the core Image model, these adapters enable the same analyzed data to drive both the interactive TUI (rendered by cmd/dive/cli/internal/ui/v1) and automated outputs like JSON export or CI rule evaluation, ensuring consistent analysis logic across all usage modes.
Have a question about this repo?
These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →