To work with Zephyr, you’ll need to install a tool called West, which helps manage several tasks like building, flashing, and handling your project. But keep this in mind: West is not Zephyr. West is written in Python, so you’d normally need to install it along with a bunch of Python libraries and some extra tools. Well, there’s no need to worry about that—at Modular MX, we’ve already prepared a container for you.
Setting in up Zephyr
The container ships all the necessary dependencies, including the West tool with all its Python plugins. It doesn’t include any flash/debugger tools like OpenOCD or J-Link; because we try to keep it, as light as possible, don’t worry I will tell you how to flash and debug later on. Download the container from our dockerhub
$ docker pull modularmx/zephyros:latest
In your local machine choose a directory where your project and Zephyr sour code will live. Run the container with a shared folder using the --mount
flag. And in my case the /home/user/workspace
set the working folder inside my container
$ mkdir ~/zephyr
$ cd zephyr
$ docker run -it --rm -w /home/user/workspace --mount type=bind,src="$(pwd)",dst=/home/user/workspace modularmx/zephyros:latest
Once the container is running you can run the west tool to download Zephyr from its official repositories. This is going to take a while because it will download the entire project!!, something we should avoid using a manifest, but fort the “momento” I think is worthy.
$ west init
$ west update
By now, you should have your Zephyr folder organized with the following structure, which will serve as our Workspace. This directory is where the Zephyr source code will reside, and we will not interfere with any of the sources or directories contained within it.
.
├── bootloader
├── modules
├── tools
├── .west
└── zephyr
7 directories, 0 files
Test project
Every new project we make is going to be created inside our workspace, to keep some order we will create a folder called applications and inside this folder we create one folder for every new application. This what is called a “Zephyr workspace application” and you can find more information in Application Development — Zephyr Project Documentation
.
├── bootloader
├── modules
├── tools
├── .west
├── zephyr
└─── applications/
├── app1/
├── app2/
└── app3/
Ok lets create our directory for our first project and the needed files
$ mkdir -p applications/app1/src
$ cd applications/app1
app1/src/main.c
/* Include libraries */
#include <zephyr/kernel.h>
#include <zephyr/drivers/gpio.h>
/* Delay of 1000 ms */
#define SLEEP_TIME_MS 1000
/* Get the DeviceTree alias for the "led0" alias */
#define LED0_NODE DT_ALIAS( led0 )
/* Get pin specification (device, pin, flags) for LED0 from the Devicetree */
const struct gpio_dt_spec led0 = GPIO_DT_SPEC_GET( LED0_NODE, gpios );
int main( void )
{
/* Configure led0 as output*/
gpio_pin_configure_dt( &led0, GPIO_OUTPUT );
while(1)
{
/* Toggle LED led0 */
gpio_pin_toggle_dt( &led0 );
/* Sleep for 1000ms (delay) */
k_msleep( SLEEP_TIME_MS );
}
return 0;
}
app1/prj.conf
# Disable optimizations
CONFIG_NO_OPTIMIZATIONS=y
# Include GPIO drivers in system config (KCONFIG)
CONFIG_GPIO=y
app1/CMakeLists.txt
# Minimum CMake required version
cmake_minimum_required( VERSION 3.20.0 )
# set enviroment variables like the board in use
set(BOARD nrf54l15dk/nrf54l15/cpuapp)
set(BOARD_FLASH_RUNNER jlink)
# Get Zephyr directory
find_package(Zephyr REQUIRED HINTS $ENV{ZEPHYR_BASE})
# Project name and source files
project(Blinky)
target_sources(app PRIVATE src/main.c)
Build your project using west. -p always means make a new build directory (or rewrite the existing one), while app is the name of the directory with the source files. If you want to remove the compilation binaries for wherever reason just type west build -t pristine
$ west build -p always app
...
Memory region Used Size Region Size %age Used
FLASH: 22754 B 1428 KB 1.56%
RAM: 4432 B 188 KB 2.30%
IDT_LIST: 0 GB 32 KB 0.00%
Generating files from /home/user/workspace/applications/app1/build/zephyr/zephyr.elf for board: nrf54l15dk
Using west flash command
Our container does not come with any tools to flash or debug your board, it is necessary to do it externally with wherever tools you like (Jlink, bootloader, openocd, etc…). But you can integrate one of those tools into a new container and use the west flash command, for instance the docker file below uses the previous as base and adds the JLink software.
# Fetch the modular image for Zephyr OS as baseline
FROM modularmx/zephyros:latest
# switch to "root" user
USER root
# install all the necessary dependencies
RUN apt-get update && apt-get install -qqy wget sudo libxrandr2 libfreetype6-dev libsm6 libxfixes3 libxcursor-dev \
libxcb-render-util0 libxcb-randr0 libxcb-shape0 libxcb-image0 libxkbcommon-x11-0 libx11-xcb1 libx11-xcb1 \
libxcb-icccm4 libxcb-sync1 libxcb-keysyms1 libxcb-xfixes0 libglib2.0-0 \
&& apt-get clean \
&& rm -rf /var/lib/apt/lists/*
# Download and install the JLink software package
RUN wget --post-data "accept_license_agreement=accepted" https://www.segger.com/downloads/jlink/JLink_Linux_x86_64.deb \
&& dpkg --unpack JLink_Linux_x86_64.deb \
&& rm -f /var/lib/dpkg/info/jlink.postinst \
&& dpkg --configure jlink \
&& apt install -yf \
&& rm ./JLink_Linux_x86_64.deb
# set sudo priviledge to user (terrible idea, i know)
RUN usermod -aG root user
# switchback to "user" user
USER user
Build the container with a proper name (don’t forget this has to be done outside the previous running container)
$ docker build -t west_jlink .
Run the container with the shared directory and access to our USB devices
$ docker run --rm -it -w /home/user/workspace --mount type=bind,src="$(pwd)",dst=/home/user/workspace --device=/dev/bus/usb:/dev/bus/usb west_jlink
Inside your new container type west flash, and enjoy your led achievement
$ west flash
-- west flash: rebuilding
ninja: no work to do.
-- west flash: using runner jlink
-- runners.jlink: reset after flashing requested
-- runners.jlink: JLink version: 8.54
-- runners.jlink: Flashing file: /home/user/workspace/applications/app1/build/zephyr/zephyr.hex
Debugging with Ozone
But debug is far more better than just flashing, let’s prepare a separate container where we can run Ozone to debug our program.
FROM ubuntu:latest
#install some dependencies
RUN apt-get update && apt-get install -qqy wget libxrandr2 libfreetype6-dev libsm6 libxfixes3 libxcursor-dev
# download from segger website Ozone, latest version
RUN wget --post-data "accept_license_agreement=accepted" https://www.segger.com/downloads/jlink/Ozone_Linux_x86_64.deb \
&& dpkg -i ./Ozone_Linux_x86_64.deb \
&& rm ./Ozone_Linux_x86_64.deb
# rename user ubuntu to "user" top avoid conflicts with directories
RUN usermod -l user -d /home/user -m ubuntu \
&& usermod -aG root user
USER user
Build and run the container setting directories accordingly and also share the usb port appropriately. it is much better if you make a compose file Part 4: Time to Docker Compose
$ sudo xhost +local:docker
$ docker build -t ozone .
$ docker run --rm -it -w /home/user/workspace --net=host --env DISPLAY=$DISPLAY --mount type=bind,src="$(pwd)",dst=/home/user/workspace --device=/dev/bus/usb:/dev/bus/usb ozone
With the container running type the following line to execute ozone
$ ozone -device nrf54l15_m33 -select USB -if SWD -speed 4000 -programfile applications/app1/build/zephyr/zephyr.elf
Look at this nice video in youtube Ozone – The J-Link Debugger | Introduction if this is the first time you use Ozone .

By far my favorite way to work with Zephyr is using containers with manifest of course. By the way it is possible you are experimenting some problems with Ozone such as: where are the source files?. This has to do with directories and how containers and Ozone make use of them.