How FileTrees Are Built and Stacked in Dive for Container Image Analysis

Dive constructs in-memory FileTrees from each container image layer's tar archive, then stacks them sequentially to generate a unified view of the final filesystem state.

When analyzing container images with Dive, the tool processes each layer's contents into a hierarchical structure called a FileTree. Understanding how these FileTrees are built from raw tar archives and subsequently merged through stacking is essential for grasping how Dive calculates efficiency scores and visualizes filesystem changes across image layers.

Building FileTrees from Docker Image Layers

Processing Layer Tar Archives with processLayerTar

The construction process begins in dive/image/docker/image_archive.go, where the processLayerTar function (lines 200‑210) initializes an empty tree and populates it by walking through the tar entries.

// dive/image/docker/image_archive.go:200‑210
func processLayerTar(name string, reader *tar.Reader) (*filetree.FileTree, error) {
    tree := filetree.NewFileTree()   // <‑‑ New empty tree
    tree.Name = name                 // keep the layer name for UI
    fileInfos, err := getFileList(reader)
    …
}

Constructing File Metadata and Nodes

The getFileList helper (lines 44‑48) iterates over the tar stream, converting each header into a filetree.FileInfo object via NewFileInfoFromTarHeader. These metadata objects are then inserted into the tree using AddPath (lines 44‑55 in dive/filetree/file_tree.go), which creates a hierarchy of FileNode objects.

// dive/image/docker/image_archive.go:44‑48
files = append(files, filetree.NewFileInfoFromTarHeader(tarReader, header, name))
// dive/filetree/file_tree.go:44‑55
_, _, err := tree.AddPath(element.Path, element)

The resulting FileTree contains a complete representation of the layer's filesystem, including directories, files, white‑out markers, and total size (tree.FileSize). This tree is stored in the image.Layer object for subsequent diffing and UI rendering.

Stacking FileTrees to Create a Unified Filesystem View

A container image's final filesystem is the cumulative result of all layers. Dive implements this by stacking the trees one‑by‑one, with later layers applied on top of earlier ones.

The Stack Method and Whiteout Handling

The core merging logic resides in FileTree.Stack (lines 7‑25 in dive/filetree/file_tree.go). This method walks the upper tree depth‑first and applies a graft function to each node. When IsWhiteout() detects a white‑out marker (special ".wh." entries indicating file deletion), the method removes the corresponding path from the lower tree using RemovePath. Otherwise, it adds or overwrites the path using AddPath.

// dive/filetree/file_tree.go:7‑25
func (tree *FileTree) Stack(upper *FileTree) (failed []PathError, stackErr error) {
    graft := func(node *FileNode) error {
        if node.IsWhiteout() {                     // white‑out → delete path
            err := tree.RemovePath(node.Path())
            …
        } else {                                   // normal file/dir → add/overwrite
            _, _, err := tree.AddPath(node.Path(), node.Data.FileInfo)
            …
        }
        return nil
    }
    // Walk the upper tree depth‑first, applying `graft` to every node
    stackErr = upper.VisitDepthChildFirst(graft, nil)
    return failed, stackErr
}

Stacking Multiple Layers with StackTreeRange

For operations requiring the complete image filesystem, Dive uses StackTreeRange (lines 376‑388 in dive/filetree/file_tree.go). This function creates a copy of the first tree, then iteratively applies Stack for each subsequent layer in the specified range.

// dive/filetree/file_tree.go:376‑388
func StackTreeRange(trees []*FileTree, start, stop int) (*FileTree, []PathError, error) {
    tree := trees[0].Copy()                     // start from a clean copy
    for idx := start; idx <= stop; idx++ {
        failedPaths, err := tree.Stack(trees[idx])
        …
    }
    return tree, errors, nil
}

The caller passes the slice of layer trees in chronological order (oldest to newest), resulting in a final tree that reflects the complete filesystem after all layers have been applied.

Applications in Image Analysis

Stacked trees power Dive's core analytical features. In dive/comparer/comparer.go (lines 71‑73), the Comparer builds lower and upper trees for a selected layer and calls StackTreeRange to obtain a merged view for diff calculations. Similarly, dive/filetree/efficiency.go iterates over stacked views to compute how many files changed between layers, determining image efficiency scores.

Practical Code Examples

Creating a FileTree from a Layer Tar Archive

package main

import (
    "archive/tar"
    "os"

    "github.com/wagoodman/dive/dive/filetree"
    "github.com/wagoodman/dive/dive/image/docker"
)

func main() {
    f, _ := os.Open("layer.tar")
    defer f.Close()
    tr := tar.NewReader(f)

    // Build a tree from the tar archive
    tree, err := docker.ProcessLayerTar("layer-1", tr) // see dive/image/docker/image_archive.go
    if err != nil {
        panic(err)
    }

    // Print a pretty tree representation
    println(tree.String(true)) // includes size info
}

Manually Stacking Two FileTrees

package main

import (
    "github.com/wagoodman/dive/dive/filetree"
)

func main() {
    // Assume lowerTree and upperTree were already built from layers
    var lowerTree, upperTree *filetree.FileTree

    // Stack the upper tree onto the lower one
    failed, err := lowerTree.Stack(upperTree)
    if err != nil {
        panic(err)
    }
    if len(failed) > 0 {
        // handle white‑out or add failures
    }

    // The resulting tree now represents the merged filesystem
    println(lowerTree.String(false))
}

Building the Complete Image Tree

package main

import (
    "github.com/wagoodman/dive/dive/filetree"
    "github.com/wagoodman/dive/dive/image"
)

func buildFullTree(img *image.Image) (*filetree.FileTree, error) {
    // img.Trees contains a FileTree per layer in chronological order
    return filetree.StackTreeRange(img.Trees, 0, len(img.Trees)-1)
}

Key Implementation Files

File Purpose
dive/filetree/file_tree.go Core FileTree implementation; creation, traversal, stacking, copying, diff‑marking.
dive/filetree/file_node.go Definition of a node in the tree and helper methods (AddChild, Remove, IsWhiteout).
dive/image/docker/image_archive.go Parses Docker image tarballs, builds a FileTree per layer via processLayerTar.
dive/comparer/comparer.go Uses StackTreeRange to create merged trees for diffing individual layers.
dive/filetree/efficiency.go Calculates efficiency scores by stacking ranges of trees.

Summary

  • FileTrees are built from layer tar archives using processLayerTar in dive/image/docker/image_archive.go, which populates the tree via AddPath with FileInfo metadata extracted by NewFileInfoFromTarHeader.
  • Whiteout markers are preserved during construction and processed during stacking to handle file deletions across layers.
  • Stacking merges layers using the Stack method in dive/filetree/file_tree.go, which applies upper layers onto lower ones while handling whiteouts via RemovePath and additions via AddPath.
  • StackTreeRange combines multiple layers for full image analysis, powering diff calculations in dive/comparer/comparer.go and efficiency scoring in dive/filetree/efficiency.go.

Frequently Asked Questions

What is a FileTree in Dive?

A FileTree is an in-memory hierarchical representation of a filesystem layer constructed from tar archive entries. Each node in the tree represents a file or directory and contains a FileInfo payload with metadata such as size, permissions, and modification times extracted via NewFileInfoFromTarHeader.

How does Dive handle deleted files when stacking layers?

Dive detects whiteout markers—special entries prefixed with ".wh." that indicate file deletion in Docker layers—during the Stack operation. When IsWhiteout() returns true, the method removes the corresponding path from the lower tree using RemovePath, effectively deleting the file from the cumulative filesystem view.

Can I use Dive's FileTree implementation independently of the UI?

Yes, the filetree package located at dive/filetree/file_tree.go is self-contained and can be imported independently. You can use it to build trees from tar archives, stack them with Stack or StackTreeRange, and analyze filesystem structures programmatically without running the interactive TUI.

What is the difference between Stack and StackTreeRange?

Stack is a method on FileTree that merges a single upper FileTree onto the receiver tree, handling individual layer operations and whiteout processing. StackTreeRange is a convenience function that takes a slice of FileTrees and sequentially stacks a specified range (start to stop) onto a copy of the first tree, returning the cumulative result for full image analysis.

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:

Share the following with your agent to get started:
curl -s "https://instagit.com/install.md"

Works with
Claude Codex Cursor VS Code OpenClaw Any MCP Client

Maintain an open-source project? Get it listed too →