If you are following our free Linux kernel development course, you already know how to configure and cross-compile a kernel by hand using a toolchain and a defconfig. Buildroot takes that same workflow and wraps it inside a single reproducible build system, which is why it is one of the most popular tools taught in any serious free embedded Linux course. In this lecture of our free embedded systems course, we plug a custom kernel version and a custom device tree into Buildroot’s menuconfig, then produce a bootable image with one command.
What You Will Learn
- How Buildroot’s
menuconfigmaps onto the kernel build steps you would otherwise run by hand - How to point Buildroot at a specific kernel version and an in-tree device tree source
- How a single
makeinvocation produces a complete bootable image set - How to freeze a working configuration with
savedefconfigso it can be reproduced later
Prerequisites
- Basic familiarity with cross-compilation, covered earlier in our free linux device drivers course
- A Linux host with build essentials,
git,ncursesdev headers, and a few GB of free disk space - Comfort navigating a curses-based configuration menu (the same UI as
make menuconfigfor the kernel itself)
Why Buildroot Wraps the Kernel Build
Building embedded Linux by hand means juggling at least four separate build systems: the toolchain, the bootloader, the kernel, and the root filesystem, each with its own configuration file and its own set of environment variables. Buildroot does not replace any of these tools. It generates the correct invocation for each one and stitches the outputs together into a single output/images directory. When you configure the kernel section inside Buildroot’s menuconfig, you are really telling Buildroot which source tree, which version, and which defconfig to hand to the standard kernel build machinery on your behalf.
This matters for reproducibility. A hand-written build script that hardcodes paths on your machine will not survive a teammate cloning the repository onto a different laptop. Buildroot’s configuration is a single text file, so the whole board support package – toolchain, kernel version, device tree, root filesystem packages – travels as one artifact.
Selecting a Kernel Version and Device Tree
Inside menuconfig, the Kernel submenu exposes the same choices you would set on the kernel command line yourself: which version to fetch, which defconfig fragment to seed it with, and which device tree source to build alongside it. On current Buildroot releases these options live under Kernel -> Linux Kernel, and the important fields are:
Two choices deserve extra attention. First, Kernel version can point at a tagged release, a git branch, or a custom version string if you maintain your own fork – always prefer a current long-term-support kernel for new designs rather than copying an old tutorial’s version number verbatim. Second, Device Tree Source file names only needs the base name, without the .dts extension; Buildroot resolves it against arch/<ARCH>/boot/dts/ inside the kernel tree it just checked out.
If your device tree lives outside the kernel tree – common when you are bringing up a brand-new board – switch Device tree source to In a directory and give Buildroot the path to your own .dts file. Buildroot copies it into the kernel source tree before invoking the kernel’s own DTB compiler, so it still benefits from every include file already shipped in the kernel.
Running the Build
Once the kernel section is configured alongside your toolchain, bootloader, and root filesystem choices, the entire board image is produced with a single command:
$ make
Buildroot resolves dependencies in the correct order – toolchain first, then bootloader, then kernel, then packages, then the final filesystem image – and only rebuilds the pieces that changed since the last run, similar to how a Makefile only rebuilds stale object files. On a clean checkout this first run can easily take thirty minutes to a couple of hours depending on your host machine, since it is compiling a full cross toolchain in addition to the kernel and every enabled package.
When the build finishes, the artifacts you actually flash or copy onto storage appear together in one directory:
$ ls output/images/
Image epboard.dtb rootfs.ext4 u-boot.bin u-boot.img
Compare this with a manual build: you would normally have a kernel Image and a .dtb sitting in the kernel’s own arch/.../boot directory, a bootloader binary sitting in a completely different repository, and a root filesystem you assembled by hand with BusyBox and static device nodes. Buildroot’s real value is collecting all of that into one predictable output folder every single time.
Freezing a Working Configuration
Once you have a build that boots correctly on real hardware, the full .config generated by menuconfig is far too large and machine-specific to check into version control directly – it records absolute paths, timestamps, and every default value Buildroot ever offered. Instead, save a minimal defconfig that only lists the options you actually changed:
$ make savedefconfig BR2_DEFCONFIG=configs/epboard_defconfig
$ git add configs/epboard_defconfig board/epboard
$ git commit -m "Add EP board Buildroot configuration"
Anyone who later runs make epboard_defconfig from a clean checkout reproduces your exact toolchain, kernel version, device tree, and package selection – this is the same discipline the kernel itself uses for its own arch/*/configs/*_defconfig files, just applied one layer up.
Common Mistakes and Troubleshooting
| Symptom | Likely Cause | Fix |
|---|---|---|
| Build fails with a missing DTS file error | Device tree name in menuconfig does not match a file under arch/<ARCH>/boot/dts/ | Check the exact base filename inside the checked-out kernel source, or switch to an external DTS directory |
| Kernel version mismatch after changing the version string | Buildroot cached the old kernel source download | Run make linux-dirclean before rebuilding |
| Board hangs at bootloader, never reaches the kernel | DTB not matching the board’s memory layout | Verify the correct DTS was selected and rebuilt with make linux-rebuild |
savedefconfig output looks empty | All options still match Buildroot’s defaults | Expected if you have not changed anything beyond the target architecture yet |
Best Practices
- Always pin an explicit kernel version string rather than tracking a moving branch, so builds stay reproducible months later
- Keep board-specific device trees under
board/<vendor>/<board>/so the whole board support package sits in one place - Commit the defconfig, not the full
.config, and regenerate the full config withmake epboard_defconfigon every fresh checkout - Run
make linux-menuconfigwhen you only need to tweak the kernel config without re-running the full top-level menuconfig
Summary and Key Takeaways
- Buildroot’s Kernel submenu is a thin, structured front end over the same kernel version, defconfig, and device tree choices you would set manually
- One
makeinvocation produces every image the board needs, all landing inoutput/images make savedefconfigis how a working board bring-up becomes a reproducible, version-controlled artifact
Conclusion
Configuring the kernel inside Buildroot is not a different skill from configuring the kernel by hand – it is the exact same skill, expressed through a menu that Buildroot then executes consistently every time. That consistency is precisely why Buildroot shows up in almost every free linux device drivers course and every serious production embedded Linux project: the same defconfig that built on your laptop last week will build identically on a CI server next year. Once your board boots from a Buildroot image, the next lecture in this free embedded linux course covers layering your own compiled programs onto the root filesystem using overlays.
Frequently Asked Questions
Does Buildroot download the kernel source itself?
Yes. Once you set a kernel version in menuconfig, Buildroot fetches the matching tarball or git tree automatically the first time you run make, and caches it under its download directory for reuse.
Can I use my own out-of-tree device tree with Buildroot?
Yes. Switch the device tree source option to point at an external directory containing your .dts file, and Buildroot copies it into the kernel tree before building the DTB.
Why does the first Buildroot build take so long?
The first run compiles a complete cross toolchain from source in addition to the kernel and root filesystem packages, which is far more work than a normal incremental kernel build.
What is the difference between the full .config and a defconfig?
The full .config lists every option Buildroot knows about, including defaults. A defconfig only lists the values you changed from the default, making it small enough to review and version control.
Is Buildroot only for kernel builds?
No. This lecture focuses on the kernel submenu, but Buildroot also configures the toolchain, bootloader, and hundreds of user-space packages in the same menu system.
Do I still need to understand cross-compilation to use Buildroot?
Yes. Buildroot automates the mechanics, but debugging a failed board bring-up still requires the same cross-compilation and kernel configuration knowledge taught throughout this free linux kernel development course.
Can I rebuild just the kernel without rebuilding everything else?
make linux-rebuild forces a kernel-only rebuild, and make linux-menuconfig opens just the kernel’s own configuration menu without touching the rest of the build.
Continue the Free Linux Kernel Development Course
Next up: layering your own programs onto the root filesystem with Buildroot overlays.
Next Lecture Browse the Full Course
2 Comments