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 models every image build as a Directed Acyclic Graph (DAG) where each BLOCK is a node and each NEED / BNEED directive is a directed edge. Instead of running stages one-by-one like a traditional linear build, MiniBox identifies which blocks have all their dependencies satisfied and runs them concurrently — shrinking wall-clock build time dramatically on multi-core machines.

What Is a DAG Build?

A Directed Acyclic Graph is a structure where nodes are connected by one-way edges and no path can loop back to its starting node. In a MiniBox build:
  • Nodes are BLOCK definitions.
  • Edges are NEED and BNEED directives inside those blocks.
  • No cycles are allowed — the builder will error with circular or unsatisfiable block dependencies detected if it finds one.
The key insight is that blocks with no unmet dependencies can always run at the same time. The builder exploits this by grouping blocks into sequential waves, running every block in each wave as a concurrent goroutine.

Without DAG

Sequential builds run one stage at a time — earlier stages block later ones even when they share no data.

With DAG

Independent blocks execute in parallel. Total time collapses to the length of the critical path, not the sum of all stages.

Wave-Based Execution Model

The DAG scheduler (buildFromBlocks in internal/builder/builder.go) operates in discrete rounds called waves:
1

Compute the ready set

Scan all blocks that have not yet been built. A block is ready when every block listed in its NEED and BNEED lists has already completed. All ready blocks form the current wave.
2

Launch goroutines

Every block in the wave is dispatched as a concurrent Go goroutine. Each goroutine gets a per-line prefixed writer ([blockname] …) so log output from parallel blocks never interleaves mid-line.
3

Wait for the wave

The scheduler blocks until every goroutine in the wave reports success or failure. A single block failure aborts the entire build.
4

Advance to the next wave

Completed blocks are marked done. The scheduler loops back to step 1 and re-computes the ready set. This continues until all blocks are complete.

Example: Four-Block Node.js Build

BASE alpine

BLOCK runtime          # no deps → Wave 1
    pkg nodejs
    pkg npm

BLOCK source           # no deps → Wave 1
    workdir /app
    copy . /app

BLOCK deps             # needs runtime + source → Wave 2
    NEED runtime
    NEED source
    auto-deps

BLOCK config           # needs deps → Wave 3
    NEED deps
    env PORT=3000
    env NODE_ENV=production
    port 3000

START node index.js
WaveBlocksRuns concurrently?
1runtime, source✅ Yes — no dependencies
2deps✅ Alone, but unblocked immediately after Wave 1
3config✅ Alone, unblocked after Wave 2
The total build time is roughly max(runtime, source) + deps + config, not their sum.

NEED vs BNEED Edges in the DAG

Both directives create a dependency edge — the block they name must complete before the current block can start. They differ in what they contribute to the final image:

NEED — Layer-inclusive edge

The dependency block’s filesystem layers are included in the final exported image beneath the current block’s layer.
BLOCK deps
    NEED runtime   # node/npm layers included
    NEED source    # /app source layers included
    auto-deps

BNEED — Build-only edge

The dependency block must complete first, but its layers are excluded from the final image. Artifacts must be explicitly copied with COPY FROM=.
BLOCK runtime
    BNEED builder  # builder layers NOT included
    copy FROM=builder /app/server /server
The final image layer stack is computed by taking the last block defined in the file as the implicit target and collecting only the layers reachable through transitive NEED edges from that target. Any block only referenced via BNEED (and not also via NEED) is pruned automatically.

Build Log Format

The structured log stream lets you trace exactly what the DAG scheduler is doing in real time.
[build] START image=my-app
[base] alpine:latest: already cached
[build] mode=dag blocks=4
[dag] wave=1 ready=runtime,source
[runtime] START needs= workdir=/
[source] START needs= workdir=/
[runtime] fetch nodejs...
[source] copy . -> /app
[runtime] DONE (3.2s)
[source] DONE (0.1s)
[dag] wave=1 done cached=0 built=2 cpu_time=3.3s
[dag] wave=2 ready=deps
[deps] START needs=runtime,source workdir=/app
[deps]    ---> auto-deps: detected package.json → running npm install
[deps] DONE (12.4s)
[dag] wave=2 done cached=0 built=1 cpu_time=12.4s
[dag] wave=3 ready=config
[config] START needs=deps workdir=/app
[config] CACHED (2ms)
[dag] wave=3 done cached=1 built=0 cpu_time=2ms
[dag-summary] blocks=4 cached=1 built=3
[finalize] writing 4 layer(s)
[build] DONE image=my-app manifest=a3f1c8d2e9b0 (16.1s)

Log Prefix Reference

PrefixEmitted byMeaning
[build]Build pipelineOverall build start / mode / done
[base]Base image resolverBase image fetch / cache status
[dag]DAG schedulerWave start, wave done with stats
[dag-summary]DAG schedulerFinal totals: blocks, cached, built
[block-name]Per-block goroutineSTART, CACHED, DONE, and all instruction output
[finalize]OCI writerLayer tar+gzip and manifest generation

Block Status Lines

Each block emits exactly one of these status lines:
[myblock] START needs=dep1,dep2 workdir=/app   # block is starting
[myblock] CACHED (5ms)                          # layer hash matched — skipped
[myblock] DONE (4.7s)                           # block executed and saved

Best Practices for DAG Block Structure

Canonical Four-Block Pattern

This structure maximises cache reuse and parallelism for most application types:
BLOCK runtime          # system packages — changes rarely
    pkg <package>

BLOCK source           # raw source copy — changes frequently
    workdir /app
    copy . /app

BLOCK deps             # dependency install — changes when lockfile changes
    NEED runtime
    NEED source
    auto-deps

BLOCK config           # env / port metadata — changes with config
    NEED deps
    env KEY=VALUE
    port 3000
Why separate source from deps? If they were merged, every source change would re-run npm install / pip install. Keeping them separate means only the source block’s cache is busted — deps stays cached unless package.json or requirements.txt actually changes (because COPY source content is part of the cache key).
Put slow, stable steps early in the graph. Package installation (runtime) and dependency downloads (deps) change infrequently. Source code (source) changes on every commit. Isolating them means the slow steps stay cached.

Parallelism Tips

Maximise Wave 1

The more blocks you can place at Wave 1 (no dependencies), the more parallel work happens immediately. Use NEED only when the data dependency is real.

Avoid fat NEED chains

A → B → C → D is a serial chain with zero parallelism. Ask whether each NEED is truly necessary or whether a BNEED + COPY FROM= would achieve the same result without carrying layers.

AUTO-DEPS Language Detection

When a block contains auto-deps, MiniBox inspects the current workdir inside the overlay filesystem and runs the first matching installer it finds:

Node.js

Detects: package.jsonRuns: npm installInstalls all dependencies listed in package.json into node_modules within the current workdir.

Python

Detects: requirements.txtRuns: pip install -r requirements.txtInstalls Python packages listed in the requirements file.

Go

Detects: go.modRuns: go mod downloadDownloads all module dependencies declared in go.mod into the module cache.

Rust

Detects: Cargo.tomlRuns: cargo buildCompiles the Cargo project and all its dependencies.
auto-deps always runs after all other instructions in the block have been executed, regardless of where auto-deps appears in the block. The manifest file must be present in the workdir before auto-deps executes — supply it via a NEED dependency or an explicit COPY earlier in the same block:
BLOCK deps
    NEED runtime
    NEED source        # source block copies package.json into /app
    auto-deps          # runs after all instructions → finds /app/package.json → npm install ✅

Cycle Detection

If the dependency graph contains a cycle or an unsatisfiable dependency (a NEED referencing a block that does not exist), the scheduler will error before any build work starts:
[build] ERROR: circular or unsatisfiable block dependencies detected
Check that:
  1. All NEED and BNEED arguments match exactly a BLOCK name defined in the same file (case-sensitive).
  2. No block (directly or transitively) depends on itself.

Build docs developers (and LLMs) love