Buildroot builds a cross-compiler, a Linux kernel and a complete root filesystem from source, driven by one Kconfig-style configuration file. Starting from the
qemu_arm_vexpress_defconfigthat ships with Buildroot 2026.05.1, two commands produce a bootable ARM system you can run under QEMU. The images you ship are the ones inoutput/images/;output/target/looks like a root filesystem but must never be copied to a device.
This post starts a new hands-on series on Buildroot for embedded Linux. By the end of this part you will have built a working Buildroot root filesystem for an ARM target, booted it under QEMU, and understood which generated directories are safe to ship. Later parts add your own packages, a BR2_EXTERNAL tree, kernel and bootloader integration, and reproducible image output. If the choice between build systems is still open, our earlier Yocto vs Buildroot comparison covers it; this series assumes the decision is made.
What you need
A Linux host, several gigabytes of free disk space, and a network connection. No development board is needed for this part; QEMU stands in for the hardware. On a Debian or Ubuntu host, this covers the mandatory packages the manual lists, plus the ncurses development files that menuconfig needs:
raghu@techveda.org:~$ sudo apt install build-essential diffutils patch gzip bzip2 perl tar cpio unzip rsync file bc findutils gawk wget libncurses-dev
One rule from the manual is worth stating plainly: build everything as a normal user. Buildroot never needs root, and running it as root exposes your host to any package that misbehaves during installation. The command above is the only one in this post that uses sudo.
Getting Buildroot and choosing a target
Download and unpack the current stable release — 2026.05.1 at the time of writing — from buildroot.org/downloads, and work from that directory. Buildroot ships ready-made configurations for many boards and emulated machines, one file each in configs/, and make list-defconfigs prints them all. We will use the ARM Versatile Express machine, because QEMU emulates it and Buildroot builds the matching QEMU binary for you:
raghu@techveda.org:~$ make qemu_arm_vexpress_defconfig
Now read that configuration file. It is twenty-two lines long, and reading it is the fastest way to understand what a Buildroot target definition is:
raghu@techveda.org:~$ cat configs/qemu_arm_vexpress_defconfig
Six of those lines carry most of the meaning:
BR2_arm=y
BR2_cortex_a9=y
BR2_LINUX_KERNEL_CUSTOM_VERSION_VALUE="6.18.7"
BR2_LINUX_KERNEL_DEFCONFIG="vexpress"
BR2_LINUX_KERNEL_INTREE_DTS_NAME="arm/vexpress-v2p-ca9"
BR2_TARGET_ROOTFS_EXT2_SIZE="64M"
A Cortex-A9 core, Linux 6.18.7 configured from the in-tree vexpress defconfig, the arm/vexpress-v2p-ca9 device tree, and a 64 MB ext2 root filesystem image. The remaining lines set a patch directory, DHCP on eth0, a post-image script and a host build of QEMU; work out what each one does before moving on. Note also what is absent from the whole file: no C library, no init system, no package list. Those come from Buildroot's own defaults, which we look at shortly.
make menuconfig opens the same options interactively; you can skip it for now. Start the build. Buildroot does not support top-level parallel builds by default, so make -jN is not required here — it parallelises compilation within each package instead:
raghu@techveda.org:~$ make
The first run takes a while, because the cross-compilation toolchain is built from source before anything else; later builds reuse it.
What a Buildroot root filesystem actually contains
Everything Buildroot produces lands in a single output/ directory. Two of its subdirectories decide whether your image works:
-
output/images/— the finished artefacts: kernel image, device tree blob, bootloader and root filesystem images. These are the files you put on the target. -
output/target/— almost the root filesystem, but not usable as one. Buildroot does not run as root, so it cannot create the device nodes in/dev/and cannot set the correct permissions, for example setuid on the BusyBox binary. Copying this directory to a device gives a system that does not boot correctly. If you need an extracted root filesystem, for NFS boot say, build the tarball image inoutput/images/and extract that as root instead.
Booting the image under QEMU
Every emulated machine ships a readme with the exact command line. Read it rather than guessing at QEMU options:
raghu@techveda.org:~$ cat board/qemu/arm-vexpress/readme.txt
Because the defconfig enabled BR2_PACKAGE_HOST_QEMU_SYSTEM_MODE, Buildroot has already built a matching QEMU under output/host/bin/, so your distribution's QEMU package is not needed:
raghu@techveda.org:~$ output/host/bin/qemu-system-arm -M vexpress-a9 -smp 1 -m 256 \
-kernel output/images/zImage \
-dtb output/images/vexpress-v2p-ca9.dtb \
-drive file=output/images/rootfs.ext2,if=sd,format=raw \
-append "console=ttyAMA0,115200 rootwait root=/dev/mmcblk0" \
-serial stdio -net nic,model=lan9118 -net user
The kernel boot messages appear in the terminal that started QEMU, because -serial stdio attaches the emulated serial port to your shell. A separate graphical window shows the framebuffer; you can ignore it. After a few seconds the boot ends at a login prompt:
Welcome to Buildroot
buildroot login:
Log in as root. There is no password. The banner comes from /etc/issue and the machine name from /etc/hostname, both generated by Buildroot from configuration options. To shut down cleanly, run poweroff inside the target; to force QEMU to exit, press Ctrl-A and then x.
The defaults that shape your Buildroot root filesystem
The defconfig named none of the following, which is exactly why they are worth knowing. Each is a decision you will eventually revisit:
- C library: glibc. When Buildroot builds its own toolchain, the default C library is glibc. uClibc-ng and musl are the alternatives, chosen under the Toolchain menu.
-
Init system: BusyBox. BusyBox provides a small
initthat reads/etc/inittab. Buildroot's default inittab lives inpackage/busybox/inittab; it mounts/proc, remounts the root filesystem read-write, runs/etc/init.d/rcS, and starts a getty. systemV, OpenRC and systemd are the alternatives. -
Device nodes: devtmpfs only. The kernel populates
/devitself. This needsCONFIG_DEVTMPFSandCONFIG_DEVTMPFS_MOUNTin the kernel configuration. Buildroot enables both when it builds the kernel for you, but if you bring an externally built kernel and forget them, the system will not boot. Addmdevoreudevon top once devices need firmware loading or module autoloading. - Root login: enabled, with an empty password. Convenient on a development board and unacceptable on a product. Set a password before you ship.
-
Serial console: the defconfig sets
BR2_TARGET_GENERIC_GETTY_PORT="ttyAMA0"to match the Versatile Express UART. The generic default isconsole. On a real board this option is the most common cause of "it boots but there is no login prompt".
Saving your configuration so the build is reproducible
The .config file holds thousands of lines, most at their default values, and is not the thing to commit to version control. Buildroot can strip it down for you:
raghu@techveda.org:~$ make savedefconfig BR2_DEFCONFIG=configs/techveda_vexpress_defconfig
This writes a minimal defconfig containing only the options you changed. Storing it as configs/<boardname>_defconfig is the recommended convention: it then appears in make list-defconfigs, and anyone can reproduce your configuration:
raghu@techveda.org:~$ make techveda_vexpress_defconfig
One more habit is worth forming now: Buildroot does not track configuration changes incrementally. If you change any architecture or toolchain option, an explicit make clean is required, and make distclean removes the configuration as well. Skipping this causes builds that fail in ways that make no sense.
If you would rather learn this workflow on real hardware, with board bring-up and image deployment done end to end, that is what our Embedded Linux and Yocto programme covers.
Key takeaways
- One small defconfig describes a whole Buildroot target — twenty-two lines specified our ARM system, kernel included.
-
output/images/holds what you ship.output/target/lacks device nodes and correct permissions, and must not be copied to a device. - The unstated defaults matter: glibc, BusyBox init, devtmpfs-only device management, and root login with no password.
- Each emulated machine ships a readme with a working QEMU command line, and Buildroot can build the matching QEMU.
- Use
make savedefconfigfor a minimal, reviewable configuration, andmake cleanafter any architecture or toolchain change.
What's next in this series
Part 2 covers adding packages to the image and setting up a BR2_EXTERNAL tree, so your own configuration and recipes live outside the Buildroot source directory and survive an upgrade to the next release.
Frequently asked questions
Do I need a development board to follow this part?
No. The qemu_arm_vexpress_defconfig configuration targets an ARM machine that QEMU emulates, and Buildroot builds the matching QEMU binary. A Linux host with several gigabytes of free disk space is enough.
Why should I not copy output/target/ to my device?
Buildroot runs as a normal user, so it cannot create device nodes in /dev/ and cannot set permissions such as setuid on the BusyBox binary. The directory looks complete but produces a system that does not boot correctly. Use an image from output/images/ instead.
Which C library does Buildroot use by default?
When Buildroot builds its own toolchain, the default C library is glibc. uClibc-ng and musl are selectable alternatives under the Toolchain menu.
Should I run make with -jN to speed up the build?
Not by default. Buildroot does not support top-level parallel builds in its normal configuration; it parallelises compilation within each package instead.
Further reading
-
The Buildroot user manual — requirements, output layout, /dev management, init systems, out-of-tree builds,
savedefconfig. - Buildroot release news — stable and release-candidate announcements.
- configs/qemu_arm_vexpress_defconfig at the 2026.05.1 tag.
- board/qemu/arm-vexpress/readme.txt — the reference QEMU command line.
- package/busybox/inittab — the default BusyBox inittab.
- system/Config.in — the /dev management and root password defaults.
Top comments (0)