Skip to main content

Documentation Index

Fetch the complete documentation index at: https://mintlify.com/chaitu426/minibox/llms.txt

Use this file to discover all available pages before exploring further.

MiniBox uses a content-addressed layer cache to avoid re-executing build steps whose inputs have not changed. When a block’s cache key matches an existing directory under DataRoot/layers/, the block is reported as CACHED and its entire instruction set is skipped — including network calls, compilations, and package installs. Understanding how the cache key is computed helps you structure blocks for maximum cache reuse and faster iteration cycles.

Cache Key Formula

Every block produces a single SHA-256 hash that acts as its cache key. The hash is computed from a concatenated string of all the inputs that could affect the block’s output:
layerHash = SHA256(
    parentHash          // hash of the BASE image identifier
  + dependency names    // comma-joined list of NEED block names
  + inherited workdir   // WORKDIR state inherited from dependency blocks
  + instruction stream  // concatenated type+args for every instruction
  + COPY source hashes  // content hash of each local COPY source path
  + "auto-deps"         // literal suffix if AUTO-DEPS is enabled
)
The parentHash chains the base image identity into every block, so a change to BASE cascades to all blocks even if none of their own instructions changed.
The cache key is computed entirely from inputs — it does not depend on the current timestamp, hostname, or any external state. This makes builds fully reproducible across machines that share the same DataRoot.

Cache Key Components in Detail

The parentHash starts as SHA256(cfile.BaseImage) — a hash of the base image string (e.g. "alpine:latest"). After each wave of blocks completes, the scheduler folds their names into the running hash:
// From builder.go
*currentHash = utils.GetHash(*currentHash + b.Name)
This means the parentHash passed to a block implicitly encodes the entire prior build history, not just the base image.
The block’s NEED names are included in the hash string as a comma-joined list. This means that if you rename a dependency block or add/remove a NEED edge, the cache is automatically busted for all downstream blocks.
The workdir propagated from dependency blocks is part of the key. If a NEED block’s final WORKDIR changes, all blocks that inherit from it will recompute — preventing the subtle bug of a block running in the wrong directory from a stale cache.
Every instruction in the block is serialised as <TYPE><args joined> and concatenated. The order matters — swapping two RUN commands produces a different key even if the commands are identical.
// From builder.go — instruction stream serialisation
cmdStr += string(inst.Type) + strings.Join(inst.Args, " ")
For every COPY <src> <dest> instruction, MiniBox computes a recursive directory hash of the source path on the host (utils.HashDir(src)) and appends it to the instruction stream. This is the critical piece that ensures a source code change busts the cache for the source block — even though the instruction text COPY . /app never changes.For COPY FROM=<block> instructions, the dependency block’s layer directory base name (which is itself a content hash) is used instead.
// From builder.go — COPY source hashing
if inst.Type == models.TypeCopy {
    if strings.HasPrefix(strings.ToUpper(inst.Args[0]), "FROM=") {
        // Use the dependency block's layer hash
        cmdStr += filepath.Base(ld)
    } else {
        // Hash the local filesystem path
        if contentHash, err := utils.HashDir(src); err == nil {
            cmdStr += contentHash
        }
    }
}
If the block has auto-deps set, the literal string "auto-deps" is appended to the key stream. This ensures that adding or removing auto-deps from a block always invalidates its cache.

Layer Storage Location

Built layers are stored as plain directories under:
DataRoot/layers/<hash>/
Where <hash> is the 64-character hex SHA-256 cache key. The default DataRoot is /var/lib/minibox unless overridden by MINIBOX_DATA_ROOT. A special marker file signals that a layer was successfully completed:
DataRoot/layers/<hash>/.minibox_layer_complete
The builder checks for this marker, not just the directory. If the directory exists without the marker (from a previously aborted build), MiniBox treats it as corrupted and rebuilds from scratch:
[myblock] Detected corrupted cache for a3f1c8d2, rebuilding...

Full DataRoot Layout

DataRoot/
├── index.json                  # OCI image index
├── blobs/
│   └── sha256/
│       └── <digest>            # manifest / config / layer tar+gzip blobs
├── layers/
│   └── <hash>/                 # build cache — one dir per cached block
│       └── .minibox_layer_complete
├── base_layers/
│   └── <image_tag>/            # extracted OCI base image rootfs
├── containers/
│   └── <id>/                   # container upper/work/rootfs + logs
├── extracted/                  # symlinks to layer dirs for fast runtime access
└── state.json                  # running container state

Cache Hits and Misses

A block hits the cache if DataRoot/layers/<hash>/.minibox_layer_complete exists. The build log shows the result for every block:
[deps] CACHED (3ms)      # cache hit — entire block skipped
[source] DONE (0.2s)     # cache miss — block executed and saved
The [dag-summary] line at the end of a build shows totals:
[dag-summary] blocks=4 cached=3 built=1

What Triggers a Cache Miss

Source file change

Any file under a COPY source path is modified, added, or deleted. utils.HashDir walks the entire tree and includes file content in the hash.

Instruction change

An instruction is added, removed, modified, or reordered within a block. Even a whitespace change in a RUN command busts the cache.

Dependency rename or reorder

A NEED block is renamed or the list of NEED dependencies changes.

BASE image change

The BASE directive references a different image tag. The parentHash changes and cascades to all blocks.

Inherited workdir change

A dependency block’s final WORKDIR changes, altering the inherited workdir key component.

Build cache cleared

minibox system prune --build-cache removes all DataRoot/layers/ directories, forcing a full rebuild.

Clearing the Build Cache

The build cache is separate from the OCI image store. Removing an image with minibox rmi deletes the manifest and blobs from index.json / blobs/sha256/ but leaves layers/ intact. This allows a rebuilt image to reuse cached block layers even after the previous image was removed. To force a complete rebuild of all blocks:
minibox system prune --build-cache
This removes the entire DataRoot/layers/ directory tree. The next build will recompute every block from scratch, repopulating the cache as it goes.
system prune --build-cache cannot be undone. All incremental build state is lost and the next build will take as long as the very first build.
To remove both the build cache and all container/image data:
minibox system prune

Performance Flag: MINIBOX_INDEX_LAYERS

After a block layer is saved, MiniBox optionally runs an async indexing pass (via storage.IndexLayer) that builds a lookup structure for lazy layer loading. For large layers — particularly node_modules — this indexing can be expensive. Set MINIBOX_INDEX_LAYERS=0 to disable it entirely and speed up the [finalize] phase:
MINIBOX_INDEX_LAYERS=0 minibox build -t my-app .
# Normal build finalise (with indexing):
[finalize] writing 4 layer(s)
[finalize] done (4.8s)

# With MINIBOX_INDEX_LAYERS=0:
[finalize] writing 4 layer(s)
[finalize] done (0.3s)
Indexing runs asynchronously in a goroutine and does not block the build from completing. The savings are primarily visible on large layer directories. For most small images the difference is negligible.

Tips for Maximising Cache Hits

Isolate source from deps

Keep COPY . /app in a dedicated source block. This way, source changes only bust the source block — the deps block (with slow npm install) stays cached until package.json actually changes.

Put stable steps first

System package installation (pkg nodejs) changes rarely. Place it in an early block with no NEED dependencies so it is the most cache-stable node in the graph.

Use .miniboxignore

Create a .miniboxignore file to exclude build artefacts, .git, and node_modules from COPY source hashing. Unnecessary files in the hash increase the chance of spurious cache misses.

Avoid broad COPY in dep blocks

Only COPY the exact files a block needs. COPY ./package.json instead of COPY . means a change to src/ does not bust the deps block cache.

Pin package versions

pkg nodejs@20 pins to a specific version. Unpinned packages can cause cache misses when the latest version changes in the Alpine repository.

Share a DataRoot across builds

The cache is stored on disk under DataRoot. Builds running in CI with a persistent DataRoot (e.g., mounted volume) benefit from layers cached by previous runs.

Build docs developers (and LLMs) love