Start with the Developer Experience

2026-07-03  ·  strategy, productivity, philosophy

For the [yoe] build project, I am prioritizing one thing above all else: the day-to-day embedded Linux developer experience. That includes every developer on the project (system, kernel, application, etc.), which on many projects is one person.

Perspective

When building systems, we can start from many perspectives: security, reproducibility, reliability, LTS (long-term support), control, immutability, scalability, BSP compatibility, and more. As technologists, we all have technology preferences and priorities that shape our work, and it is natural to start there. Steve Jobs started somewhere else: the customer experience. After receiving a scathing insult, he made the following comment:

You’ve got to start with the customer experience and work backwards to the technology. You can’t start with the technology and try to figure out where you’re going to try to sell it.

Fast, transparent, efficient, and approachable

I’ve received many good critiques about the project to date. This feedback is useful and shapes the implementation going forward. But I need to state the project’s goals more clearly, so it’s obvious why [yoe] exists rather than reaching for an existing alternative. The first priority: the system must be fast, transparent, efficient, and approachable for small teams. A great small-team developer experience lets us solve the real problem, building the product, without fuss or meta-work (work around the work) that doesn’t move us toward the goal.

[yoe] must start fast and do its job quickly with almost no overhead. The UI (both command-line and TUI) must be intuitive, discoverable, and pleasant to use. The build system’s metadata should be obvious and easy to read. Problems should be easy to debug. Information about a component should stay in one place, not scattered across the system where it’s hard to piece together. The same process application developers use should translate directly into building the product software, with no extra work. SDK snapshots must disappear; rather the build system should dynamically construct the needed build environment for any component. We must reuse the work others have done (building, packaging, security updates, etc.). Nothing should be built more than once, unless something has changed. It should be painless to move around the system, make, build, deploy, and capture changes. This tool must help small teams move fast and get things done. All other concerns, such as 100% build from source, binary reproducibility, etc., are secondary.

The importance of iteration speed

One of the most important ingredients for innovation is iteration. The faster you iterate, the more you can innovate. This is critical for startups, where the entire point is innovation. Yocto does many things well, but fast iteration is not one of them, especially when things change or problems occur. [yoe] build trades some of this control and flexibility for speed.

Do the simplest thing well

There are many advanced things you can do with Linux: containers, advanced file-system techniques such as overlays, immutability, OSTree, Nix builds/stores, etc. However, the immediate goal of the [yoe] build project is to deliver the best possible developer experience for building and deploying a simple read/write Embedded Linux system root file system including the bootloader, kernel, and applications. We are starting at ground zero with the simplest case that is more than adequate for many projects. Immutability, containers, extensions, and other things will follow, but the foundational piece must be right - an efficient way to create a custom root file system for an embedded device.

Technology tradeoffs

Go and Starlark are pragmatic choices that reflect the project’s values. While they will never be as safe and reproducible as technologies like Rust and Nix, they are easier to read and understand. We may switch to something else later if we can maintain the developer experience. But the bet is that we can borrow good ideas (caching, hashing, etc.) from other systems to reach an acceptable level. Most technology decisions are a compromise. Reaching near 100% in one area often requires significant compromises in another. For example, the Nix language is powerful and excellent at completely specifying a system. It is straightforward to use existing packages and configs, but writing new Nix expressions or debugging problems is hard. There is a steep learning curve. This is the trade-off - if you want the Nix level guarantees and flexibility when specifying a system, then you also need to accept its costs and complexities. The same can be said for Rust. If you need Rust level guarantees with memory safety, then you need to deal with the complexities of the borrow checker and slow compile time. It is fine to insist on 100% security, reproducibility, compile from source, etc. if these are real needs and you have the resources to do it (time, dedicated people, and skills). Small teams often don’t have any of these three luxuries.

Maybe none of this matters, because AI can figure out and handle complex systems. AI is effective. I’ve debugged some genuinely hard problems in Yocto with Claude Code. However, it’s still slow. Requiring AI to do everything makes for a slow, miserable development experience. Making things simpler for humans also makes it easier for AI. Efficiency, iteration speed, and context size matter, even in the world of AI. Although AI can work on large tasks autonomously, the speed from human idea to working result is still the bottleneck for innovation. If we have to wait minutes/hours/days for AI and the build system to do its work for every iteration, that is too slow.

[yoe] build is similar to Go in that it aims to be pretty good at most things, but likely not the best at any particular technical requirement. Go is not the fastest, safest, most expressive, … language. But it is pretty good at everything and the developer experience is amazing. Likewise, [yoe] will not be the most secure, controlled, extensible, … build system, but it will be good enough in all of these areas, and the developer experience will be amazing. There is a risk that without focusing on a technical requirement, such as security, up front, we won’t be able to get to where we need to be. However, there is an even greater risk that if we focus exclusively on a single technical requirement, the developer experience will suffer.

Other systems exist for other reasons

Other systems accommodate other priorities:

  • If BSP compatibility and building from source is your top priority, then use Yocto - it does this very well.
  • If package count or absolute reproducibility is your top priority, then use Nix - it is amazing.
  • If containers are your focus, then check out Torizon.
  • If you want to leverage the mobile ecosystem, then use Android.
  • If you want a clean way to manage application layers, check out Avocado OS.
  • If you want enterprise backing and guarantees, then use Ubuntu Core.

But don’t say “just use X.” If X had the developer experience I wanted, I would already be using it. However, we can always learn from other systems, and [yoe] build is mostly just a combination of good ideas from existing systems in a different balance.

Here is one example of navigating this tension: support for upstream distribution packages. Tool startup time matters, so the tool had to be written in a fast compiled language. I also wanted to use the vast package ecosystem from distributions like Alpine and Debian. Instantiating a unit for every package in these ecosystems was too expensive and slow, and it bloated the dependency graph. We could have stopped and said - well, if we want lots of packages, startup time will be slow. Instead, we compromised on only creating virtual units for the packages we actually use (a dependency of any image), but the full package repository is still available for inclusion in an image. As a bonus, the UI is not cluttered with packages we are not using. There are solutions to most problems if we are creative in finding them. But we must preserve the overriding values and goals through this process; otherwise we soon get locked into a situation where attaining them is impossible.

Summary

In summary, [yoe] build is a pragmatic tool for fast iterations and an excellent day-to-day developer experience. Everything else is secondary. It will likely never be the best at any single technical requirement, but it balances them well enough to help small teams iterate quickly and build innovative edge products they can maintain. [yoe] lets developers focus on the problem they are solving, not the details of the build system. Like the Go language, the tooling and build system is nearly invisible, even boring.

In your day-to-day development of edge/embedded systems, what is important to you? What would you like to see different?