Hello. In the previous lesson, you configured and built a QEMU system with Buildroot, then proved that its kernel, root filesystem, console, and BusyBox init formed a working boot contract. You also saw the key output areas: host, staging, target, and images.
This lesson begins the investigative part of the Buildroot prototype module. You will treat a package build as a traceable supply path, locating its source input, unpacked build tree, build-time tools and sysroot, runtime files, and final deployable image. This is a necessary habit for later work on custom packages, patches, boot failures, and product release evidence.
The important correction to make at the start is this: a Buildroot package does not necessarily leave a file in every directory. A target application such as BusyBox normally ends up in target and then the root filesystem image, but it may not install development files into staging. A host-only helper tool belongs in host and should never reach the target image. The directories describe roles in the integration system, not a mandatory seven-step physical path for every package.
One Buildroot build, several file populations
Keep the following categories distinct:
| Location | What it contains | Primary consumer | Should it be deployed? |
|---|---|---|---|
Download directory, usually dl/ or BR2_DL_DIR | Retrieved source archives, source snapshots, and sometimes cached Git material | Buildroot fetch and extract logic | No |
output/build/ | Per-component source trees, object files, generated files, build logs, and stamps | The package build itself | No |
output/host/ | Programs that execute on Ubuntu, including Buildroot helper tools and cross-toolchain components | Buildroot and package build commands | No |
output/staging/ | Target headers, unversioned library links, and target development libraries | Compilation and linking of later target packages | No |
output/target/ | A runtime-oriented target root filesystem tree | Root filesystem image generation | Not directly |
output/images/ | Bootable and flashable outputs: root filesystem images, kernel, bootloader, DTBs, and related artifacts | QEMU, bootloader, flashing process, release pipeline | Yes |
A useful way to think about the boundaries is:
- The download directory records an input obtained from outside the build.
- The build directory is the package’s private construction site.
- The host directory provides tools that run on the development workstation.
- The staging directory provides target development interfaces to other packages.
- The target directory receives files needed at runtime.
- The images directory contains finalized deployable representations.
The same source project can appear in more than one role. For example, Buildroot may build host-pkgconf because the host needs pkgconf during compilation, while it may separately build a target-side pkgconf if the final device needs that command. Those are different package variants with different installation destinations.
Buildroot - Usage and documentation
Read this Buildroot usage reference for its concise distinction between build, host, staging, target, and images directories. Its terminology is stable across Buildroot releases, though exact subdirectory names can vary with the configured architecture and toolchain.
In the section describing Buildroot output, read the directory breakdown. Focus especially on why staging contains development material, why target is only an intermediate filesystem tree, and why the final bootable output is under images.
The two meanings of “host”
This terminology can initially be confusing:
- Your host machine is the Ubuntu laptop on which Buildroot runs.
output/host/contains binaries that execute on that host machine.- A target package is compiled for QEMU’s emulated architecture, not for Ubuntu.
output/staging/contains target-architecture headers and libraries used while compiling target packages.
With an external toolchain, output/host/ commonly includes an imported or wrapped cross-toolchain. Modern Buildroot installations also commonly make output/staging a symbolic link into a target sysroot beneath output/host/. Do not treat that symlink as proof that staging files are host binaries: inspect an ELF file if you are unsure of its architecture.
For example, a header at:
output/staging/usr/include/zlib.h
is not executable at all. It is a target development interface. A library such as:
output/staging/usr/lib/libz.so
is often an unversioned linker name used during compilation. The corresponding runtime object, when the package is selected for the target, is more likely to look like:
output/target/usr/lib/libz.so.1
The unversioned .so and headers are usually omitted from a production root filesystem because they are development material. This separation is exactly what prevents a target image from silently turning into a development SDK.
Read the package pipeline as evidence, not as a black box
Buildroot describes package work through logical targets such as source, extract, patch, configure, build, install-staging, and install-target. Internally, completion is represented by stamp files in the package’s build directory.
Read the relevant parts of Bootlin’s Buildroot Common Usage guide. It explains both the persistent source-cache role of BR2_DL_DIR and the package build stages that leave evidence in the output tree.
First, under “Location of downloaded packages,” read the download-cache discussion. Notice that an environment setting can override the value recorded in .config. Then, under “Package-specific make targets,” read the ordered package stages. Relate install-staging to build-time target development files, install-target to runtime files, and the host-package install step to output/host.
The stage names do not guarantee that every stage performs work:
- A target application may only perform
install-target. - A target library commonly performs both
install-stagingandinstall-target. - A host package installs into
output/host; it has no target runtime installation. - A kernel, bootloader, or device tree can be built in
output/buildand copied directly tooutput/images, without becoming a normal file inoutput/target. - A locally overridden source tree can deliberately bypass the normal download and extraction path.
This is why “Where is package X?” is not a single-directory question. The better question is: Which package role is this, and which outputs should that role produce?
Establish a directory map for your existing QEMU build
Reuse the variables from the previous lesson. If you started a new terminal, redefine the paths to match your environment:
export BR="$HOME/work/buildroot"
export LAB="$HOME/work/buildroot-qemu-lab"
export CACHE="$LAB/source-cache"
export OUT="$HOME/build-output/qemu-external"
First, confirm which directories actually exist and whether staging is a symbolic link:
for d in build host staging target images; do
printf '\n%-8s ' "$d"
if test -e "$OUT/$d" || test -L "$OUT/$d"; then
readlink -f "$OUT/$d"
du -sh "$OUT/$d"
else
echo "not present"
fi
done
ls -ld "$OUT/staging"
A typical result shows:
- many package-specific subdirectories under
build; - a substantial
hosttree; stagingresolving into a sysroot associated with the target tuple;- a smaller
targettree; - a compact
imagesdirectory containing the kernel and filesystem artifacts.
Now inventory the component build directories:
find "$OUT/build" -mindepth 1 -maxdepth 1 -type d \
-printf '%f\n' | sort | sed -n '1,100p'
You will usually see a mixture of names such as:
busybox-<version>
linux-<version>
host-fakeroot-<version>
host-mkpasswd-<version>
The host- prefix is meaningful. It usually identifies a package variant that Buildroot built to execute on your Ubuntu workstation during the build. It does not mean the package is intended for the target.
Inspect a few executables from host:
find "$OUT/host/bin" -maxdepth 1 \
\( -type f -o -type l \) -printf '%f\n' | sort | sed -n '1,80p'
find "$OUT/host" -type d -path '*/sysroot/usr/include' -print
The first command lists utilities Buildroot may invoke while building. The second helps locate the target sysroot associated with the external cross-toolchain. The exact directory containing the sysroot depends on your target tuple and Buildroot release.
A concrete trace: BusyBox from source cache to image
BusyBox is a good first trace because it is present in the minimal QEMU system and you can see its runtime result clearly. It is also useful precisely because it demonstrates that a target package need not populate staging.
Set the package name and locate its build directory:
PKG="busybox"
PKGDIR="$(find "$OUT/build" -mindepth 1 -maxdepth 1 -type d \
-name "${PKG}-*" -print | head -n 1)"
printf 'Package build directory: %s\n' "$PKGDIR"
If this prints an empty path, do not guess at a version number. Re-run the directory inventory and confirm that the supplied configuration enabled BusyBox:
grep '^BR2_PACKAGE_BUSYBOX=' "$OUT/.config" || true
Once PKGDIR is valid, inspect its stamps and top-level contents:
find "$PKGDIR" -maxdepth 1 -type f -name '.stamp_*' \
-printf '%f\n' | sort
find "$PKGDIR" -maxdepth 1 -mindepth 1 \
-printf '%y %f\n' | sort | sed -n '1,80p'
Depending on the Buildroot release and package infrastructure, stamp names commonly indicate completed download, extraction, patching, configuration, build, and installation work. These stamps are Buildroot’s local evidence that it does not need to repeat a completed step.
Locate the source input
Inspect the BusyBox package metadata in the Buildroot source tree:
grep -nE '^(BUSYBOX_VERSION|BUSYBOX_SOURCE|BUSYBOX_SITE)' \
"$BR/package/busybox/busybox.mk"
Then look for the cached source archive. Use your lab’s approved cache path, replacing "$CACHE" if the resolved BR2_DL_DIR setting identifies another location:
find "$CACHE" -type f -iname 'busybox*' -printf '%f\n' | sort
The package metadata tells Buildroot what version and source location it expects. The download cache contains the retrieved input. The extracted source tree and all compilation products belong under output/build/busybox-<version>.
This is an important provenance boundary:
package/busybox/busybox.mksays what Buildroot intends to obtain.- The download cache contains the fetched source input.
- The build directory contains the particular unpacked and built instance.
- The configuration and Buildroot revision tell you which metadata rules governed the build.
A valid cache is not merely a performance optimization. Along with the Buildroot revision and configuration, it is part of the evidence needed to recreate an offline build using the same input versions.
Locate the runtime installation
BusyBox normally installs its multicall executable and applet links into the target tree:
find "$OUT/target" -type f -name busybox -printf '%p\n'
for f in "$OUT/target/bin/sh" "$OUT/target/sbin/init"; do
if test -e "$f" || test -L "$f"; then
printf '%s: ' "$f"
readlink -f "$f"
fi
done
file "$OUT/target/bin/busybox" 2>/dev/null || true
The expected relationship is that /bin/sh and /sbin/init resolve to BusyBox or are BusyBox applet links. Their presence in target means Buildroot has prepared them for the runtime filesystem.
Now deliberately check the staging tree:
find "$OUT/staging" -type f -name busybox -printf '%p\n' 2>/dev/null
An empty result is normally correct for BusyBox. BusyBox is primarily a runtime program in this minimal configuration; other target packages generally do not compile by including BusyBox headers or linking to a BusyBox library.
So the BusyBox trace is:
| Evidence point | Expected BusyBox result |
|---|---|
| Download cache | BusyBox source archive or another configured source form |
output/build/ | busybox-<version> source and build tree, with stamps |
output/host/ | Build tools may be used, but BusyBox itself is not installed as a host utility by this target-package build |
output/staging/ | Usually no BusyBox development installation |
output/target/ | /bin/busybox plus shell, init, and other applet links |
output/images/ | The final root filesystem image contains the runtime BusyBox files |
This is a complete package map even though two locations have an intentional absence.
Trace a target library and observe staging versus target
A library package is the clearest way to see why Buildroot has both staging and target trees. First, list actual shared libraries in your generated runtime filesystem:
find "$OUT/target" -type f -name '*.so.*' \
-printf '%p\n' | sort | sed -n '1,80p'
find "$OUT/staging/usr/include" -type f \
-printf '%p\n' 2>/dev/null | sort | sed -n '1,40p'
Do not select the C library as your first example if you are using an external toolchain. Its headers and libraries may have arrived as part of the external-toolchain import rather than as a separately selected Buildroot target package.
Instead, choose an identifiable optional library that appears in both locations, if your configuration includes one. For a package called foo, the expected pattern is:
| Tree | Typical evidence for a target library |
|---|---|
| Download cache | foo-<version> source archive, Git snapshot, or configured source material |
build/ | foo-<version> build directory and stamps |
host/ | Cross-compiler and host build utilities used to compile foo |
staging/ | foo.h, libfoo.so, static archives, pkg-config metadata |
target/ | Versioned runtime library such as libfoo.so.1 and runtime configuration, if required |
images/ | The runtime library inside the chosen root filesystem image |
The unversioned libfoo.so is usually a development linker interface. The versioned libfoo.so.1 is normally what a dynamically linked target program loads at runtime. This distinction later becomes central when packaging versioned libraries and diagnosing missing shared-library failures.
Confirm the final image rather than trusting target
Buildroot’s target directory is close to the final root filesystem but is not itself the deployable artifact. Image generation applies filesystem-specific ownership, permissions, device-node rules, compression, and layout.
List your image outputs:
find "$OUT/images" -maxdepth 1 -type f \
-printf '%f\t%s bytes\n' | sort
find "$OUT/images" -maxdepth 1 -type f -name 'rootfs.*' \
-exec file {} \;
If your image format is a tar archive, inspect it without extracting:
tar -tf "$OUT/images/rootfs.tar" | grep -E '(^|/)busybox$|(^|/)bin/sh$|(^|/)sbin/init$'
If your output is SquashFS and Buildroot produced unsquashfs, inspect its listing read-only:
"$OUT/host/bin/unsquashfs" -l "$OUT/images/rootfs.squashfs" \
2>/dev/null | grep -E '/(busybox|bin/sh|sbin/init)$'
Use only the command matching an image file that actually exists. Do not mount the image merely to inspect it; mounting adds unnecessary privilege and cleanup concerns. A file listing is sufficient to prove that an expected runtime path made it into the final artifact.
For ext2 or ext4 output, debugfs can inspect the filesystem image without mounting it if it is available in the Buildroot host tools:
DEBUGFS="$(find "$OUT/host" -type f -name debugfs -print | head -n 1)"
printf 'debugfs: %s\n' "$DEBUGFS"
The final image is the artifact you hash, boot in QEMU, flash to a board, and eventually release. The target tree is evidence about what Buildroot intends to put there, but images is the final deployment boundary.
Use the map for diagnosis and controlled rebuilds
Two practical rules follow from this layout.
First, Buildroot does not maintain a general reverse database saying which package owns every file in output/staging and output/target. Therefore, removing a package or manually deleting its files from those shared trees is unsupported. A file found in target is evidence, but authoritative diagnosis requires examining the relevant package metadata, its build directory, stamps, and installation commands.
Second, output/build is disposable integration state. Do not develop by editing a package’s extracted source tree and expecting the change to survive cleanup. The build directory can be removed by make clean, and Buildroot can re-extract the source. Later, when you develop a gateway application, you will keep its source under Git and use a proper package definition or Buildroot’s source-override mechanism.
For now, retain a compact directory-map record with your existing build evidence:
mkdir -p "$LAB/evidence"
{
echo "Buildroot revision:"
git -C "$BR" rev-parse HEAD 2>/dev/null || true
echo
echo "Resolved download setting:"
grep '^BR2_DL_DIR=' "$OUT/.config" || true
echo
echo "BusyBox build directory:"
printf '%s\n' "$PKGDIR"
echo
echo "BusyBox stamps:"
find "$PKGDIR" -maxdepth 1 -type f -name '.stamp_*' \
-printf '%f\n' | sort
echo
echo "Deployable images:"
find "$OUT/images" -maxdepth 1 -type f \
-printf '%f\t%s bytes\n' | sort
} > "$LAB/evidence/buildroot-package-map.txt"
find "$OUT/images" -maxdepth 1 -type f -print0 |
sort -z |
xargs -0 sha256sum > "$LAB/evidence/qemu-images.sha256"
This record is small, but it gives you a defensible answer to several real engineering questions: which Buildroot revision was used, what source cache was intended, where BusyBox was built, which stages completed, and precisely which image artifacts were produced.
You can now distinguish Buildroot’s six important storage roles: source inputs in the download cache, isolated per-component construction in build, host-executed tooling in host, target development interfaces in staging, runtime content in target, and deployable output in images.
The key takeaway is that package tracing is role-aware. BusyBox reaches the runtime tree but normally not staging; a target library commonly reaches both staging and target; a host helper reaches only host; and a kernel often goes straight from its build tree to images. This prevents several common errors: copying staging into an image, deploying target directly, treating host tools as target binaries, or deleting shared-tree files to remove a package.
Next, you will customize the Buildroot root filesystem with a skeleton overlay and a deterministic post-build script. The directory map from this lesson will let you verify exactly where that customization enters the build and how it appears in the final image.
Can't find a good explanation? Sign up and we'll make it for you
Sign up