TL;DR
- Patching SDK internals in an NCS global install silently breaks every other project on your machine.
- Freestanding (manual
ZEPHYR_BASEbinding) → T2 (app ownswest.ymlmanifest) → T3 (separate manifest repo managing multiple apps and SDK) gives you progressive isolation.- Start with Freestanding. Move to T2 or T3 only when reproducibility or multi-product needs demand it.
When you first pick up the nRF Connect SDK (NCS), the natural move is to follow Nordic's Toolchain Manager or VS Code Extension wizard. When I set up my Zephyr dev environment with Antigravity IDE, I did exactly that. A few button clicks and the Zephyr RTOS core, libraries, and compiler land neatly under C:\ncs\toolchains or a v2.x.x folder.
The problem: this convenient global install becomes an unmanageable swamp as projects multiply.
Zephyr's ecosystem is well designed. Need to remap board pins? Use an app.overlay (Devicetree Overlay). Want to tweak system configuration? Edit prj.conf (Kconfig) and override without touching the original source.
But production firmware development never follows the textbook. Sometimes you have to reach deep into the HAL (Hardware Abstraction Layer) to dodge a chipset errata. Sometimes the only way forward is a monkey patch inside Nordic's nrfxlib.
In a global install, that single hack silently breaks every other NCS project on your machine. On top of that, every new SDK release meant downloading gigabytes onto the main drive all over again.
Under the banner of convenience, I had lost control of my build environment.
Deep Dive: True Isolation and the Essence of Freestanding
Breaking this cycle required flipping the NCS paradigm. Instead of "the Toolchain Manager owns my SDK," the goal was "my code picks and isolates its own SDK."
The first concept I ran into was the Freestanding Application.
In the Zephyr ecosystem, a manifest (west.yml) is a dependency recipe file that declares "this project needs Zephyr version X, nRF module version Y." Think of it as the equivalent of Node.js's package.json or Python's requirements.txt. One west update command pulls every source at the exact pinned version.
Freestanding skips the manifest and any complex topology. It is the most intuitive way to isolate an SDK on a local dev machine. My app lives at D:\Workspace\my_app\, the SDK sits in a completely separate location, and I bind ZEPHYR_BASE via an environment variable only in the terminal session where I build.
This is like mounting just the volumes you need into a Docker container — clean and minimal. It is the lightest starting point for physically separating your project from the SDK.
But as projects grow, Freestanding hits its limits. That is where Zephyr's official West Workspace Topology comes in. Zephyr defines three topologies based on who owns the manifest repository:
- T1 (Zephyr-centric Star): Zephyr itself is the manifest. This is what you get from a default
west init. - T2 (App-centric Star): Your app is the manifest. The cleanest layout for a single product.
- T3 (Forest): A dedicated manifest repo manages multiple apps and the SDK as siblings. Built for multi-product teams.
This post walks through the progression from Freestanding to T2, then T3.
Step-by-step progression from global install to Freestanding, T2, and T3
Implementation: Building a Controlled Environment from Scratch
Here is the step-by-step journey from the simplest Freestanding setup, through T2, to T3 Topology.
Step 1: Pure Freestanding — The Lightest Isolation
First, I stepped out of the Toolchain Manager's shadow and installed the toolchain and West (Zephyr's meta-tool) directly inside a Python virtual environment (venv).
Open a terminal at the app directory and manually bind the SDK dependency. On Windows, a single script call does it:
:: Temporarily bind the system NCS (Zephyr) directory to this session only
> C:\ncs\v2.x.x\zephyr\zephyr-env.cmd
:: Now the build command targets the SDK on the C drive
> west build -b nrf52840dk_nrf52840
For a quick library test or a lightweight side project, this is enough. The binding lives only inside the virtual environment and leaves everything else untouched.
Step 2: T2 Topology — Let the App Own Its Dependencies
Freestanding relies on your memory or documentation to track the SDK version. Once you start building a real product, that weakness shows. A teammate should be able to git clone and reproduce the exact build environment — telling them over Slack which ZEPHYR_BASE to set is an accident waiting to happen.
T2 is what Zephyr's official docs call the Star topology (application is the manifest repository). You place a west.yml manifest directly inside your app repo, making the app itself the owner of its SDK dependencies.
my_product/ # App repo IS the manifest repo (T2)
├── .west/ # Auto-generated by west init
├── app/ # Application source
│ ├── CMakeLists.txt
│ ├── prj.conf
│ └── src/main.c
├── west.yml # ★ Pins Zephyr, NRF, and module versions
├── zephyr/ # Pulled by west update
└── modules/ # nrf, hal_nordic, nrfxlib, etc.
Pin a specific Zephyr tag or commit hash in west.yml, and anyone who runs west init -l . && west update anywhere gets the exact same SDK version. The reproducibility that Freestanding lacked is now baked in.
Zephyr's Manifest Imports feature simplifies west.yml authoring considerably. Instead of listing dozens of module versions by hand, you import the Zephyr manifest wholesale and only override what you need.
The caveat: T2 assumes one app = one manifest. For a single product it is hard to beat, but the moment you need to develop Product A and Product B on the same hardware platform, the model starts cracking.
Step 3: T3 Topology — Forest Structure for Multiple Apps
The moment arrived when I had to develop "Terminal A" and "Terminal B" on the same platform hardware simultaneously. Creating separate T2 workspaces for each meant duplicating multi-gigabyte SDK folders per product.
The answer was T3 Topology. Zephyr's docs call it the Forest topology — a dedicated manifest repository arranges multiple apps and the SDK as siblings at the same directory level.
my_workspace/ # Workspace root (T3 anchor)
├── .west/ # West metadata
├── manifest_repo/ # Single repo holding west.yml (dependency hub)
├── app_product_a/ # Application 1
├── app_product_b/ # Application 2
├── zephyr/ # Zephyr core pulled by West
└── modules/ # nrf, hal, nrfxlib, etc.
The decisive difference from T2: the manifest lives outside any app. A single manifest_repo/west.yml governs the Zephyr version, module versions, and even the Git revisions of every app. app_product_a and app_product_b remain fully decoupled, yet at build time they safely share the same verified SDK within my_workspace.
T3 also pays off when setting up CI/CD pipelines (e.g., GitHub Actions). The CI server clones manifest_repo, runs west update, and every app plus its dependencies land at the correct version in one shot.
Troubleshooting: The Curse of Windows Long Paths (260-char Limit)
The moment I moved to a West Workspace structure (T2 or T3) and wired up CI, builds started failing:
"No such file or directory"
NCS and Zephyr have deeply nested directory hierarchies. When CMake and Ninja generate build artifacts on top of that, the path easily blows past the Windows MAX_PATH limit of 260 characters. Unless you are using third-party tools that handle long paths natively, you will hit this.
If you are building this structure on Windows, open an admin PowerShell and run this before anything else:
# Disable the Windows 10/11 long path restriction (reboot recommended after)
New-ItemProperty -Path "HKLM:\SYSTEM\CurrentControlSet\Control\FileSystem" -Name "LongPathsEnabled" -Value 1 -PropertyType DWORD -Force
I missed this one setting and spent half a day chasing phantom CMakeLists.txt errors.
Key differences between the three topology options at a glance
Takeaway: Which Structure Should You Pick?
After ditching the Toolchain Manager's global install, we gained three weapons for NCS project management.
There is no silver bullet. Each structure carries clear trade-offs, and the right choice depends on your project's expected lifetime and team size.
| Freestanding | T2 (Star) | T3 (Forest) | |
|---|---|---|---|
| Manifest | None | App = Manifest | Separate manifest repo |
| App count | 1 | 1 | Multiple |
| SDK version pinning | Manual (ZEPHYR_BASE) |
Pinned via west.yml
|
Pinned via west.yml
|
| Reproducibility | Low | High | High |
| Initial setup cost | Near zero | Medium | High |
| Storage | Minimal | One SDK copy per app | One shared SDK copy |
1. Freestanding — When You Need Isolation Right Now
- Best for: One-off side projects, quick sensor driver tests you plan to throw away, getting a build environment running in under 5 minutes on a personal machine.
- Downside:
westdoes not manage versions for you. You have to remember or document which SDK version you depended on, and manually bind the environment variable every time. Three months later — or when handing the project to a teammate — reproduction may fail.
2. T2 (Star) — When You Are Building a Real Product
- Best for: Serious single-product development where anyone should be able to
git cloneand reproduce the exact build environment. A solo developer or small team focused on one firmware project. - Downside: The entire SDK lives inside the app workspace, so adding a second product means duplicating the same Zephyr/NRF sources. Storage and
west updatetime scale linearly with product count.
3. T3 (Forest) — When the Team Manages Multiple Products
- Best for: Company-scale development with multiple products (A, B, C...) on the same hardware platform sharing common core logic. CI/CD pipeline integration is a must at this stage.
- Downside: Significant learning curve for the initial
manifest_repo/west.ymlsetup and directory structure conventions. A manifest maintainer must mediate version conflicts across products.
My own path: I started with Freestanding for rapid prototyping, moved to T2 once the product was greenlit, and switched to T3 when derivative products appeared on the same platform. You do not need to start at T3. Binding the SDK with a single zephyr-env.cmd call via Freestanding is enough. That alone is the first step toward reclaiming control in the closed NCS ecosystem.


Top comments (0)