32blogby StudioMitsu
Archive8 min read

Building a Custom Linux for Raspberry Pi with Yocto: Complete Guide

Step-by-step guide to building a custom Linux image for Raspberry Pi using Yocto. Covers meta-raspberrypi setup, local.conf configuration, SD card flashing, and Wi-Fi/SSH/GPIO setup.

"I want to run my own custom Linux on a Raspberry Pi."

"I don't want Raspbian — I want to build the OS entirely from scratch."

"I'm prototyping an IoT product on Raspberry Pi and need a stripped-down, controlled OS."

If any of these describes you, Yocto Project is exactly what you need. This guide walks you through building a custom Linux image for Raspberry Pi using Yocto — from downloading the source layers all the way to booting on real hardware.

What You'll Need

  • Build machine: Ubuntu 22.04/24.04, 16+ GB RAM, 200+ GB disk
  • Raspberry Pi 4 or 5
  • microSD card: 16 GB+ (Class 10 recommended)
  • SD card reader
  • HDMI monitor + keyboard (for initial boot verification)

Prerequisites

  • You understand Yocto fundamentals (BitBake, layers, recipes)
  • You've successfully built core-image-minimal with Poky
  • You're comfortable with basic Linux command-line operations

New to Yocto? Read the Yocto Beginner's Guide first.

What You'll Learn

  • How to set up the meta-raspberrypi layer
  • How to configure Raspberry Pi-specific build settings
  • How to write the image to an SD card
  • How to boot and verify on real hardware
  • How to enable Wi-Fi, SSH, and GPIO

The Overall Workflow

  1. Download Poky and the required layers
  2. Add layers to bblayers.conf
  3. Set MACHINE in local.conf
  4. Build with bitbake
  5. Write the image to an SD card
  6. Boot and verify on Raspberry Pi

Step 1: Download the Required Layers

bash
# Create a working directory
$ mkdir -p ~/yocto && cd ~/yocto

# Download Poky (styhead = latest stable)
$ git clone -b styhead git://git.yoctoproject.org/poky.git

# Download meta-raspberrypi
$ git clone -b styhead git://git.yoctoproject.org/meta-raspberrypi

# Download meta-openembedded (required by meta-raspberrypi)
$ git clone -b styhead git://git.openembedded.org/meta-openembedded

# Verify the directory layout
$ ls
meta-openembedded  meta-raspberrypi  poky

Use the same branch (styhead) for all three repositories. Mixing branches is a common source of mysterious compatibility errors.

Step 2: Initialize the Build Environment

bash
$ cd ~/yocto/poky
$ source oe-init-build-env build-rpi

# BitBake automatically moves you to the build-rpi/ directory
# conf/local.conf and conf/bblayers.conf are auto-generated on first run

Naming the build directory build-rpi keeps it separate from any other build targets you might have (like a QEMU build).

Step 3: Add the Layers

bash
# Add meta-oe (dependency required by meta-raspberrypi)
$ bitbake-layers add-layer ../../meta-openembedded/meta-oe

# Add meta-python (optional — needed if you're using Python packages)
$ bitbake-layers add-layer ../../meta-openembedded/meta-python

# Add meta-networking (optional — needed for networking tools)
$ bitbake-layers add-layer ../../meta-openembedded/meta-networking

# Add meta-raspberrypi
$ bitbake-layers add-layer ../../meta-raspberrypi

# Verify all layers are active
$ bitbake-layers show-layers
layer                 path                                             priority
================================================================================
meta                  /home/user/yocto/poky/meta                       5
meta-poky             /home/user/yocto/poky/meta-poky                  5
meta-yocto-bsp        /home/user/yocto/poky/meta-yocto-bsp             5
meta-oe               /home/user/yocto/meta-openembedded/meta-oe       6
meta-raspberrypi      /home/user/yocto/meta-raspberrypi                9

Step 4: Configure local.conf

Edit conf/local.conf with Raspberry Pi-specific settings:

bash
$ nano conf/local.conf

Add or modify the following:

bash
# ========================================
# Raspberry Pi build configuration
# ========================================

# Target board
# raspberrypi4-64  : Raspberry Pi 4 (64-bit)
# raspberrypi5     : Raspberry Pi 5
# raspberrypi3-64  : Raspberry Pi 3 (64-bit)
MACHINE = "raspberrypi4-64"

# Parallel build settings (tune to your CPU core count)
BB_NUMBER_THREADS = "8"
PARALLEL_MAKE = "-j 8"

# Generate SD card image in wic format
IMAGE_FSTYPES = "wic.bz2 wic.bmap"

# GPU memory allocation (MB)
GPU_MEM = "64"

# Enable UART for serial console debugging
ENABLE_UART = "1"

# Enable SSH server (Dropbear)
IMAGE_FEATURES += "ssh-server-dropbear"

# Allow root login without a password (development only — disable for production)
EXTRA_IMAGE_FEATURES += "debug-tweaks"

# Use U-Boot bootloader
RPI_USE_U_BOOT = "1"

Supported MACHINE Values

MACHINETarget BoardArchitecture
raspberrypi5Raspberry Pi 564-bit (aarch64)
raspberrypi4-64Raspberry Pi 464-bit (aarch64)
raspberrypi4Raspberry Pi 432-bit (armhf)
raspberrypi3-64Raspberry Pi 364-bit (aarch64)
raspberrypi3Raspberry Pi 332-bit (armhf)
raspberrypi2Raspberry Pi 232-bit (armhf)
raspberrypi0-2w-64Raspberry Pi Zero 2 W64-bit (aarch64)

I recommend 64-bit targets for Pi 3 and newer. Memory management is better and 64-bit is where the ecosystem is heading.

Step 5: Build the Image

bash
# Build a minimal image (smallest footprint)
$ bitbake core-image-minimal

# Or build a base image with slightly more tooling
$ bitbake core-image-base

# Expected build times (first run):
# - 8 cores / 32 GB RAM:  ~1.5–2 hours
# - 4 cores / 16 GB RAM:  ~3–4 hours

Verifying the Build Output

bash
# List the generated files
$ ls tmp/deploy/images/raspberrypi4-64/

core-image-minimal-raspberrypi4-64.wic.bz2     # Compressed SD card image
core-image-minimal-raspberrypi4-64.wic.bmap    # Block map for fast flashing
Image                                           # Kernel image
bcm2711-rpi-4-b.dtb                            # Device tree blob

Step 6: Flash the SD Card

bmaptool uses the .bmap file to skip empty blocks during writing. It's significantly faster than dd for sparse images.

bash
# Install bmaptool
$ sudo apt install bmap-tools

# Check which device is your SD card
$ lsblk

# Flash the image (replace /dev/sdX with your actual device)
$ cd tmp/deploy/images/raspberrypi4-64/
$ sudo bmaptool copy core-image-minimal-raspberrypi4-64.wic.bz2 /dev/sdX

# Sync to ensure all writes complete
$ sync

Method 2: dd (Traditional)

bash
# Decompress and write in one command
$ bzcat core-image-minimal-raspberrypi4-64.wic.bz2 | sudo dd of=/dev/sdX bs=4M status=progress

$ sync

Warning: Always double-check the /dev/sdX device name with lsblk before running dd. Writing to the wrong device will overwrite your host system's hard drive. There is no undo.

Step 7: First Boot on Raspberry Pi

  1. Insert the SD card into your Raspberry Pi
  2. Connect an HDMI monitor and keyboard
  3. Connect power and let it boot
  4. When the login prompt appears, log in as root (no password)
Poky (Yocto Project Reference Distro) 5.1 raspberrypi4-64 ttyAMA0

raspberrypi4-64 login: root

root@raspberrypi4-64:~# uname -a
Linux raspberrypi4-64 6.6.x-yocto-standard #1 SMP ... aarch64 GNU/Linux

root@raspberrypi4-64:~# cat /etc/os-release
NAME="Poky (Yocto Project Reference Distro)"
VERSION="5.1 (styhead)"

You're running a fully custom Linux built from source. Everything in that image — every binary, every library — was cross-compiled specifically for this hardware by your build machine.

Additional Configuration: Wi-Fi, SSH, GPIO

Enabling Wi-Fi

Add to conf/local.conf:

bash
IMAGE_INSTALL:append = " linux-firmware-bcm43455 wpa-supplicant"

You can pre-configure Wi-Fi credentials with a wpa_supplicant.conf file included via a .bbappend on the wpa-supplicant recipe.

SSH Remote Login

Since you already added ssh-server-dropbear to IMAGE_FEATURES, SSH is ready to go.

bash
# Check the IP address (with wired connection)
root@raspberrypi4-64:~# ip addr show eth0

# Connect from your development machine
$ ssh root@192.168.1.xxx

In practice, I use SSH for all day-to-day work after the first boot. The HDMI monitor is just for verifying the initial boot.

GPIO Control

bash
# Add GPIO tools to local.conf
IMAGE_INSTALL:append = " libgpiod libgpiod-tools"

After rebuilding and reflashing:

bash
root@raspberrypi4-64:~# gpioinfo
root@raspberrypi4-64:~# gpioset gpiochip0 17=1    # Set GPIO17 HIGH
root@raspberrypi4-64:~# gpioget gpiochip0 27       # Read GPIO27

Troubleshooting

Black screen — nothing on HDMI

Cause: HDMI output configuration doesn't match your monitor.

Fix: Add to conf/local.conf:

bash
HDMI_FORCE_HOTPLUG = "1"
HDMI_GROUP = "2"
HDMI_MODE = "82"   # 1080p 60Hz

Kernel panic — not syncing

Cause: Mismatch between kernel and root filesystem.

Fix: Rebuild the image cleanly and re-flash the SD card. If the card seems flaky, try formatting it first with a tool like fdisk or just use a different card.

wic.bz2 image not found

ERROR: No such file or directory: ...wic.bz2

Cause: IMAGE_FSTYPES doesn't include wic.

Fix: Add this to conf/local.conf:

bash
IMAGE_FSTYPES = "wic.bz2 wic.bmap"

INCOMPATIBLE_LICENSE error

ERROR: xxx has incompatible license

Cause: A package with commercial licensing requirements was included.

Fix: Explicitly accept the license in local.conf:

bash
LICENSE_FLAGS_ACCEPTED = "commercial"

Summary

Here's what we covered:

  • meta-raspberrypi provides all the Raspberry Pi board support
  • MACHINE setting selects the exact target board
  • wic format creates a ready-to-flash SD card image
  • bmaptool is the fastest way to write images to SD cards

You now have a completely custom Linux running on Raspberry Pi — no Raspbian, no pre-installed bloat, just exactly what you put in the image. Strip out everything you don't need, add your own applications, and you have the foundation for a production IoT device.

The same Yocto workflow scales from prototype to production. That's one of the big reasons Yocto is worth the upfront learning investment.

Next Steps


This article reflects the state of Yocto Project and meta-raspberrypi as of January 2026. Procedures may change as new versions are released.