Build and run projects with Zephyr

Zephyr is a small-footprint, scalable real-time OS for resource-constrained devices, maintained under the Linux Foundation. It's Apache-licensed, supports 450+ boards out of the box, and ships with a coherent build system (west), a Linux-style devicetree, and Kconfig. This post walks through the shortest possible path: from nothing built to "Hello World" running on QEMU x86, then how to retarget the same sample to real hardware.

Pre-requisites

Before you build anything, make sure you have:

The official getting-started guide for macOS / Linux / Windows covers the SDK install, west bootstrap, and the first workspace clone:

docs.zephyrproject.org/latest/develop/getting_started

About west

west is Zephyr's meta-tool. It wraps Git (for managing the workspace's many repos), CMake (for builds), and the toolchain glue (for flash/debug). You almost never call cmake or make directly — you call west and it does the right thing.

Build and run Hello World on QEMU x86

QEMU lets you run a Zephyr binary on your host CPU — no board, no flash cable. Perfect for first-time exploration.

cd zephyr/
west build -b qemu_x86 samples/hello_world
west build -t run

The first command builds the hello_world sample for the qemu_x86 target. The second invokes the run CMake target, which boots the resulting ELF inside QEMU. You should see:

*** Booting Zephyr OS build v3.x.x ***
Hello World! qemu_x86

Press Ctrl+A then X to exit QEMU.

Retarget to real hardware

Same sample, swap the board. For an STM32F4 Discovery:

west build -p auto -b stm32f4_disco samples/hello_world
west flash

For a Nordic nRF52840 DK:

west build -p auto -b nrf52840dk_nrf52840 samples/hello_world
west flash

The -p auto flag triggers a pristine build whenever the board changes — without it, CMake caches stale settings and you get cryptic errors.

Useful west commands

Build directory layout

After a build, the build/ directory contains everything CMake produced:

build/
├── CMakeCache.txt
├── zephyr/
│   ├── zephyr.elf       ← linked firmware (debug symbols)
│   ├── zephyr.bin       ← raw binary for flashing
│   ├── zephyr.hex       ← Intel HEX for some flashers
│   ├── zephyr.map       ← link map
│   └── .config          ← resolved Kconfig (final values)
└── ...

The .config file is gold for debugging "why is this option not enabled?" questions — it shows the actual values applied after all defaults, board overlays, and prj.conf merges.

Common troubleshooting

Where to go next

← Back to all posts