In a previous post I promised a future post detailing how I created my custom Raspberry Pi Zero W based PID controller. This post will discuss the basics of creating a custom OS for the Raspberry Pi using Buildroot.

Getting started

Starting from a Debian Bookworm machine, we’ll need a few packages:

$ apt-get update
$ apt-get install -y --no-install-recommends \
    bc \
    build-essential \
    ca-certificates \
    cpio \
    file \
    libncurses-dev \
    rsync \
    unzip \

We’ll create a new git project, and add Buildroot as a submodule.

$ mkdir os && pushd os && git init
$ git submodule add
$ git commit -m "add buildroot"

Many Raspberry Pi devices are supported out of the box.

$ ls -1 buildroot/configs/raspberrypi*

We could build a bootable rootfs for the Raspberry Pi Zero W right now.

$ make -C buildroot raspberrypi0w_defconfig
$ make -C buildroot

The config even has post-image scripts that create a block image we can dd straight onto an SD card and boot. Another such script enables the UART serial console, so we have some way of interacting with the device.


Since you’re reading a post about the Zero W, you’re probably interested in getting this thing connected to wifi. That will require a little customization of our Buildroot project.

The Buildroot manual discusses two methods of customizing a buildroot project. You can simply fork Buildroot, and add commits on top of it, or you can use a br2-external tree. Coming from a Yocto background, I have a slight preference towards maintaining my changes in an external tree. It’s worth noting that while this bears a superficial resemblance to a Yocto layer, an external tree is not as complex (or powerful), and there really isn’t a huge difference between Buildroot’s two supported approaches. Using the br2-external-tree can help you review the totality of your modifications without cooking up a git incantation.

As an aside, Buildroot’s manual is a great resource. If you’re used to Yocto’s endless reams of understructured, duplicative, yet somehow incomplete documentation, don’t assume Buildroot is the same.

We’ll add a br2-external-tree and copy our device’s defconfig into our tree to modify down the road.

$ mkdir -p tuxpresso/config
$ cp buildroot/config/raspberrypi0w_defconfig tuxpresso/config/tuxpresso0w_defconfig

Now we build with

$ make -C buildroot BR2_EXTERNAL=$(pwd)/tuxpresso tuxpresso0w_defconfig
$ make -C buildroot

We’ll enable the wifi drivers and WPA supplicant. We can now run modprobe brcmfmac to enable the wifi driver. If we want this done automatically, we can enable mdev, a stripped down version of the device event manager udev.

Now let’s configure our wifi settings. We’ll want to add a rootfs overlay, and I’ve chosen to copy the WPA supplicant configuration from the FAT formatted /boot partition of the SD card every time the system boots. I probably should have mounted the filesystem as read only, since we would really be unhappy if the system somehow decided to corrupt /boot. Since our interface configuration doesn’t have any secrets in it, I’ve simply added it to the overlay.

I haven’t checked recent versions of Buildroot, but I had to disable wifi power saving in order to get ssh login working reliably. haveged is installed to speed up boot by providing faster random number generator initialization. I also copied my ssh public keys onto the system in a manner similar to the WPA supplicant configuration.

What’s next?

I see a lot of fun directions to go from here. We haven’t added any of our secret sauce to this custom operating system yet! Another interesting topic would be stripping this kernel down to the bones, and seeing how fast we can get this thing to boot.