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

> Discover how Dive builds and stacks FileTrees from container image layers to create a unified filesystem view for efficient analysis. Learn the inner workings of this powerful tool.

- Repository: [Alex Goodman/dive](https://github.com/wagoodman/dive)
- Tags: internals
- Published: 2026-03-07

---

**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`](https://github.com/wagoodman/dive/blob/main/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.

```go
// 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`](https://github.com/wagoodman/dive/blob/main/dive/filetree/file_tree.go)), which creates a hierarchy of `FileNode` objects.

```go
// dive/image/docker/image_archive.go:44‑48
files = append(files, filetree.NewFileInfoFromTarHeader(tarReader, header, name))

```

```go
// 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`](https://github.com/wagoodman/dive/blob/main/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`.

```go
// 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`](https://github.com/wagoodman/dive/blob/main/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.

```go
// 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`](https://github.com/wagoodman/dive/blob/main/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`](https://github.com/wagoodman/dive/blob/main/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

```go
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

```go
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

```go
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`](https://github.com/wagoodman/dive/blob/main/dive/filetree/file_tree.go) | Core `FileTree` implementation; creation, traversal, stacking, copying, diff‑marking. |
| [`dive/filetree/file_node.go`](https://github.com/wagoodman/dive/blob/main/dive/filetree/file_node.go) | Definition of a node in the tree and helper methods (`AddChild`, `Remove`, `IsWhiteout`). |
| [`dive/image/docker/image_archive.go`](https://github.com/wagoodman/dive/blob/main/dive/image/docker/image_archive.go) | Parses Docker image tarballs, builds a `FileTree` per layer via `processLayerTar`. |
| [`dive/comparer/comparer.go`](https://github.com/wagoodman/dive/blob/main/dive/comparer/comparer.go) | Uses `StackTreeRange` to create merged trees for diffing individual layers. |
| [`dive/filetree/efficiency.go`](https://github.com/wagoodman/dive/blob/main/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`](https://github.com/wagoodman/dive/blob/main/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`](https://github.com/wagoodman/dive/blob/main/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`](https://github.com/wagoodman/dive/blob/main/dive/comparer/comparer.go) and efficiency scoring in [`dive/filetree/efficiency.go`](https://github.com/wagoodman/dive/blob/main/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`](https://github.com/wagoodman/dive/blob/main/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.