"I want to bundle my own application into a Yocto Linux image."
"What exactly is a recipe? I have no idea how to write a .bb file."
"How do I build a GitHub project with Yocto?"
This guide answers all three. I'll walk you through creating Yocto recipes from the ground up, with working code examples at every step.
Once you understand how recipes work, you can package virtually any software into a Yocto image. That's when embedded Linux development really opens up.
Prerequisites
- You understand Yocto fundamentals (BitBake, layers)
- You've successfully built
core-image-minimalwith Poky - You're comfortable with basic Linux commands
New to Yocto? Start with the Yocto Beginner's Guide first.
What You'll Learn
- The structure of a Yocto recipe (.bb file)
- Building a Hello World recipe from scratch
- Key variables: SRC_URI, do_compile, do_install
- Fetching source from GitHub
- Using CMake, Autotools, and Meson via
inherit - Adding packages to your Linux image
What Is a Yocto Recipe?
A recipe is a .bb file that describes how to build and install a specific piece of software. Think of it as a self-contained build script that answers four questions:
- Where to get the source code (
SRC_URI) - How to build it (
do_compile) - Where to install it (
do_install) - What license it uses (
LICENSE)
Recipe File Naming
Recipe filenames follow the pattern packagename_version.bb:
hello-world_1.0.bb # hello-world version 1.0
myapp_2.3.1.bb # myapp version 2.3.1
busybox_1.36.1.bb # busybox version 1.36.1
BitBake automatically extracts the package name (PN) and version (PV) from the filename. No need to set them explicitly.
Basic Recipe Structure
# hello-world_1.0.bb
# ===== Metadata =====
SUMMARY = "Hello World sample"
DESCRIPTION = "A minimal example recipe for learning Yocto"
LICENSE = "MIT"
LIC_FILES_CHKSUM = "file://${COMMON_LICENSE_DIR}/MIT;md5=0835ade698e0bcf8506ecda2f7b4f302"
# ===== Source acquisition =====
SRC_URI = "file://hello.c"
# ===== Source directory =====
S = "${WORKDIR}"
# ===== Build =====
do_compile() {
${CC} ${CFLAGS} ${LDFLAGS} -o hello hello.c
}
# ===== Install =====
do_install() {
install -d ${D}${bindir}
install -m 0755 hello ${D}${bindir}
}
Key Variables
Package Metadata
| Variable | Description | Example |
|---|---|---|
SUMMARY | Short one-line description | "Hello World sample" |
DESCRIPTION | Detailed description | "A minimal learning example..." |
LICENSE | License identifier | "MIT", "GPLv2", "Apache-2.0" |
LIC_FILES_CHKSUM | License file checksum | file://LICENSE;md5=xxx |
SRC_URI | Where to get the source | "file://", "git://", "https://" |
S | Where source is unpacked | $/git |
Build and Install Variables
| Variable | Description | Expands to |
|---|---|---|
${WORKDIR} | Working directory for this build | /tmp/work/.../hello-world/1.0-r0/ |
${D} | Staging root for installation | .../image/ |
${bindir} | /usr/bin path | /usr/bin |
${libdir} | /usr/lib path | /usr/lib |
${sysconfdir} | /etc path | /etc |
${CC} | Cross-compiler | arm-poky-linux-gnueabi-gcc |
Pay close attention to ${D}. In do_install, you always prepend ${D} to the install path. This installs files into the staging area, not the host system. BitBake handles moving them to the final image.
Hands-On 1: Building a Hello World Recipe
Let's build the simplest possible recipe and confirm it works end-to-end.
Step 1: Create a Custom Layer
Always put your recipes in your own layer — never in Poky's layers directly.
# Initialize the build environment
$ cd ~/yocto/poky
$ source oe-init-build-env build
# Create a new layer
$ cd ..
$ bitbake-layers create-layer meta-mylayer
# Add it to the build
$ cd build
$ bitbake-layers add-layer ../meta-mylayer
# Verify
$ bitbake-layers show-layers
layer path
=========================================================
meta /home/user/yocto/poky/meta
meta-poky /home/user/yocto/poky/meta-poky
meta-yocto-bsp /home/user/yocto/poky/meta-yocto-bsp
meta-mylayer /home/user/yocto/meta-mylayer
Step 2: Create the Directory Structure
$ mkdir -p ../meta-mylayer/recipes-example/hello-world/files
# Expected structure:
# meta-mylayer/
# ├── conf/
# │ └── layer.conf
# ├── COPYING.MIT
# ├── README
# └── recipes-example/
# └── hello-world/
# ├── hello-world_1.0.bb
# └── files/
# └── hello.c
Step 3: Write the Source Code
// files/hello.c
#include <stdio.h>
int main(void) {
printf("Hello from Yocto!\n");
printf("This app was built with a custom recipe.\n");
return 0;
}
Step 4: Write the Recipe
# hello-world_1.0.bb
SUMMARY = "Hello World - Yocto recipe learning example"
DESCRIPTION = "A minimal application demonstrating how to write a custom Yocto recipe"
AUTHOR = "Your Name <your@email.com>"
HOMEPAGE = "https://example.com"
LICENSE = "MIT"
LIC_FILES_CHKSUM = "file://${COMMON_LICENSE_DIR}/MIT;md5=0835ade698e0bcf8506ecda2f7b4f302"
# Source comes from the local files/ directory
SRC_URI = "file://hello.c"
S = "${WORKDIR}"
do_compile() {
# Use the Yocto-provided cross-compiler
${CC} ${CFLAGS} ${LDFLAGS} -o hello hello.c
}
do_install() {
# Create /usr/bin in the staging area
install -d ${D}${bindir}
# Install the binary with executable permissions
install -m 0755 hello ${D}${bindir}
}
Step 5: Build the Recipe
$ bitbake hello-world
# Success looks like:
NOTE: Tasks Summary: Attempted 123 tasks of which 120 didn't need to be rerun
If you hit an error, check the log files under tmp/work/x86_64-linux/hello-world/.
Step 6: Include It in Your Image
Add this to conf/local.conf:
IMAGE_INSTALL:append = " hello-world"
Rebuild and test in QEMU:
$ bitbake core-image-minimal
$ runqemu qemux86-64 nographic
# After boot:
root@qemux86-64:~# hello
Hello from Yocto!
This app was built with a custom recipe.
That's a complete round-trip: write a recipe, build it, and run it on your custom Linux system.
Hands-On 2: Fetching Source from GitHub
In real projects, you'll usually fetch source from an external repository rather than shipping it locally.
Example: Adding nlohmann/json
# nlohmann-json_3.11.3.bb
SUMMARY = "JSON for Modern C++"
DESCRIPTION = "A header-only JSON library for C++"
HOMEPAGE = "https://github.com/nlohmann/json"
LICENSE = "MIT"
LIC_FILES_CHKSUM = "file://LICENSE.MIT;md5=f5f7c71504da070bcf4f090205ce1080"
# Fetch from GitHub using HTTPS
SRC_URI = "git://github.com/nlohmann/json.git;protocol=https;branch=master"
# Pin to a specific commit — always do this for reproducibility
SRCREV = "9cca280a4d0ccf0c08f47a99aa71d1b0e52f8d03"
# Git clones go to ${WORKDIR}/git
S = "${WORKDIR}/git"
inherit cmake
EXTRA_OECMAKE = "-DJSON_BuildTests=OFF"
Always pin SRCREV to a specific commit hash. Avoid AUTOREV — it breaks reproducible builds.
SRC_URI Patterns
| Source type | SRC_URI format |
|---|---|
| Local file | file://hello.c |
| Git (HTTPS) | git://github.com/user/repo.git;protocol=https;branch=main |
| Git (SSH) | git://git@github.com/user/repo.git;protocol=ssh;branch=main |
| HTTP/HTTPS tarball | https://example.com/file.tar.gz |
| Patch file | file://fix-build.patch |
Using Inherit Classes
Yocto ships with classes that encapsulate common build patterns. Using inherit saves you from writing boilerplate do_compile and do_install tasks.
| Class | Use Case | What It Automates |
|---|---|---|
cmake | CMake projects | cmake → make → make install |
autotools | Autoconf projects | configure → make → make install |
meson | Meson projects | meson → ninja → install |
python3native | Python scripts | setup.py install |
systemd | systemd services | Service enablement |
CMake Project Example
# myapp-cmake_1.0.bb
SUMMARY = "My CMake App"
LICENSE = "MIT"
LIC_FILES_CHKSUM = "file://${COMMON_LICENSE_DIR}/MIT;md5=0835ade698e0bcf8506ecda2f7b4f302"
SRC_URI = "git://github.com/user/myapp.git;protocol=https;branch=main"
SRCREV = "abc123def456..."
S = "${WORKDIR}/git"
inherit cmake
EXTRA_OECMAKE = "-DBUILD_TESTS=OFF -DCMAKE_BUILD_TYPE=Release"
With inherit cmake, BitBake handles cross-compilation cmake invocation, build, and installation automatically.
Autotools Project Example
# myapp-autotools_1.0.bb
SUMMARY = "My Autotools App"
LICENSE = "GPLv2"
LIC_FILES_CHKSUM = "file://COPYING;md5=..."
SRC_URI = "https://example.com/myapp-1.0.tar.gz"
SRC_URI[md5sum] = "abc123..."
SRC_URI[sha256sum] = "def456..."
inherit autotools
EXTRA_OECONF = "--disable-docs"
Registering as a systemd Service
For embedded devices, you often want your application to start automatically at boot.
# myservice_1.0.bb
SUMMARY = "My System Service"
LICENSE = "MIT"
LIC_FILES_CHKSUM = "file://${COMMON_LICENSE_DIR}/MIT;md5=0835ade698e0bcf8506ecda2f7b4f302"
SRC_URI = "file://myservice.c \
file://myservice.service"
S = "${WORKDIR}"
inherit systemd
# Specify the systemd unit file
SYSTEMD_SERVICE:${PN} = "myservice.service"
SYSTEMD_AUTO_ENABLE = "enable"
do_compile() {
${CC} ${CFLAGS} ${LDFLAGS} -o myservice myservice.c
}
do_install() {
install -d ${D}${bindir}
install -m 0755 myservice ${D}${bindir}
# Install the systemd unit file
install -d ${D}${systemd_system_unitdir}
install -m 0644 myservice.service ${D}${systemd_system_unitdir}
}
Common Errors and Fixes
LIC_FILES_CHKSUM mismatch
ERROR: License checksum mismatch...
Cause: The MD5 hash in your recipe doesn't match the actual license file.
Fix: Recalculate the correct hash:
$ md5sum LICENSE
do_fetch failed
ERROR: Fetcher failure: Unable to find file...
Cause: Wrong path in SRC_URI, or network issue when fetching from Git.
Fix: Double-check your SRC_URI and SRCREV. Make sure the commit hash exists in the repository.
Nothing PROVIDES 'hello-world'
ERROR: Nothing PROVIDES 'hello-world'
Cause: The layer containing your recipe isn't in bblayers.conf.
Fix: Add the layer:
$ bitbake-layers add-layer ../meta-mylayer
$ bitbake-layers show-layers # verify it's listed
SRC_URI checksum mismatch
ERROR: SRC_URI checksum mismatch...
Cause: Checksum for an HTTP/HTTPS download isn't set or doesn't match.
Fix: Calculate the correct checksum:
$ sha256sum downloaded-file.tar.gz
Summary
Here's what to remember about writing Yocto recipes:
- Recipes are
.bbfiles that describe a complete build process - Core variables:
SRC_URI,do_compile,do_install - Always put your recipes in a custom layer, not in Poky
- Use
inheritto automatically handle CMake/Autotools/Meson projects - Add packages to an image with
IMAGE_INSTALL:append - Always pin
SRCREVto a specific commit hash for reproducibility
Once you're comfortable writing recipes, you can package anything into a Yocto image. The next natural steps are deploying to real hardware and building a complete custom distro layer.
Next Steps
- Yocto Project Complete Beginner's Guide
- Yocto Layers Explained: Creating and Adding meta-xxx
- Building Linux for Raspberry Pi with Yocto
This article reflects the state of Yocto Project as of January 2026. Procedures may change as new versions are released.