Fuchsia, the Operating System Google Built Without Linux

In May 2021 a Google Nest Hub sitting on someone’s kitchen counter downloaded a routine update, rebooted, and came back up running a completely different operating system. Same home screen, same photo frame, same alarms. Underneath, the Linux kernel was gone.

That is the part I keep thinking about. Not the ambition of Fuchsia, but the fact that Google performed an OS transplant on a shipping consumer device and the only people who noticed were the ones reading the technical information page in Settings.

I have been following this project on and off since the repository first appeared, mostly out of curiosity about the kernel. This post is my attempt to write down what Fuchsia actually is, why it exists, and what it has to show for seven years of work.


Seven years of a repository nobody announced

In August 2016 a repository named fuchsia showed up on Google’s git servers with no blog post, no press release, and a one-line description: “Pink + Purple == Fuchsia (a new Operating System)”. That was the entire announcement. People found it by accident.

The next few years went roughly like this. In 2017 the project grew a graphical shell called Armadillo, built in Flutter, and screenshots of it went around as proof that Google was building an Android replacement.

Fuchsia's Armadillo shell in July 2017 Fuchsia’s Armadillo UI, July 2017. Source: Wikimedia Commons (Apache License 2.0)

At Google I/O 2019, Hiroshi Lockheimer addressed it directly and said Fuchsia was an experiment with new operating system concepts, and that people should not assume it was a phone OS. The same year the project got a documentation site at fuchsia.dev. In December 2020 it got the rest of the apparatus of a real open source project: public mailing lists, a published roadmap, an issue tracker, and an RFC process for design decisions.

Then in May 2021 it shipped. The first-generation Nest Hub replaced its Cast-based software with Fuchsia, with no visible change to the interface. The Nest Hub Max followed in 2022, and in May 2023 the second-generation Nest Hub started receiving Fuchsia builds.

The naming is exactly as unserious as it looks. Zircon, the kernel, was originally called Magenta and was renamed in 2017. Escher, Scenic, Garnet, Topaz, Starnix: the project names read like a gemstone catalogue, and several of the layers they referred to no longer exist.

2023 has been less kind. In January, Google’s company-wide layoffs hit the Fuchsia team, and reporting from The New York Times and 9to5Google put the cut at around 16% of a roughly 400-person org. In July, the team removed support for Assistant speakers from the tree, marking the Nest Mini, Nest Audio, and Nest Wifi as unsupported after having planned to bring Fuchsia to them.

So: an operating system with seven years of development, three shipping product lines, all of them smart displays, and a team that just got smaller.


Why not just use Linux?

Google runs more Linux than almost anyone alive. Android, Chrome OS, and the fleet that serves search results are all Linux. Writing a new kernel is an odd thing to do from that position, so the question is worth taking seriously.

The reasons Google has given publicly are architectural, and they show up in the project’s stated principles: simple, secure, updatable, performant. Each one maps to a specific complaint about how things work today.

The update story is the one Android has been fighting its entire life. A device ships with a kernel, a vendor BSP, and a pile of out-of-tree drivers, and the whole stack has to be rebuilt and re-certified by the OEM to move forward. Project Treble in 2017 and Project Mainline in 2019 were both attempts to cut seams into that stack after the fact. Linux does not help here, by design: the kernel deliberately has no stable in-kernel driver ABI, on the theory that drivers belong in the tree where they can be fixed along with everything else. That is a defensible engineering position and also the reason a three-year-old phone is stuck.

Security is the second. A Unix process starts with authority it never asked for. It can see the whole filesystem, it can walk /proc, it has a user ID that grants it things. We then spend enormous effort taking that authority back with namespaces, cgroups, seccomp, SELinux, and containers. Fuchsia’s answer is to never hand it out in the first place.

Form factors are the third, and the least discussed. The original repo mentioned everything up to and including traffic lights. A smart display with no touchscreen, or a speaker with no screen at all, is not what Android’s application model was designed around, even if it can be made to work.

There is a fourth motivation you see in commentary, and it is worth flagging as commentary rather than fact. Plenty of analysts have argued that Fuchsia is really about licensing and control: the GPL obligations of the Linux kernel, the years of Oracle litigation over the Java APIs in Android, and the leverage OEMs hold over the update pipeline. Fuchsia is BSD, MIT, and Apache 2.0 throughout, and Google controls delivery directly on the devices it ships. I find the update-control argument plausible and the GPL argument mostly overstated, since Android already keeps nearly everything above the kernel under permissive licenses. Google has never framed the project this way publicly, so treat it as inference.

Linux / AndroidFuchsia
Kernel scopeMonolithic: drivers, filesystems, network stack in kernelZircon handles threads, memory, IPC, scheduling; the rest is user space
Default authorityAmbient: user IDs, global filesystem, /procNone. A new process holds only the handles it was given
Driver ABIDeliberately unstable, drivers live in-treeA stable driver ABI is an explicit goal, not yet delivered
Update unitSystem image, per vendorPackage, resolved by content hash
IPCMany mechanisms, mostly hand-rolled protocolsOne IDL (FIDL) over one primitive (channels)


Zircon

Zircon is where Fuchsia stops being a variation on anything familiar.

It started as a fork of LK (Little Kernel), an embedded kernel written by Travis Geiselbrecht, and grew a process model, user mode, and a capability system on top. Zircon is 64-bit only and assumes it is running on a machine with real resources, which is the opposite of what LK was for.

The kernel exposes around a hundred syscalls, and almost all of them are non-blocking. The exceptions are the ones whose entire job is to wait: zx_object_wait_one, zx_object_wait_many, zx_port_wait, zx_nanosleep. Everything else is an object operation.

That word “object” is load-bearing. Zircon does not export a filesystem to user space. It exports kernel objects, you talk to an object through a handle, and a handle carries a set of rights.

ObjectWhat it is
Job / Process / ThreadThe scheduling and resource hierarchy. Jobs contain processes, and limits apply down the tree
ChannelBidirectional message pipe carrying bytes and handles. The backbone of all IPC
VMOVirtual Memory Object: a chunk of memory you can map, share, or resize
PortWhere a thread parks to wait on signals from many objects at once
Event / FutexSignalling and userspace locking primitives

The consequences of the handle model are bigger than they first look. There is no fork(), because forking means inheriting an entire address space and an entire authority set implicitly. Process creation goes through fuchsia.process.Launcher, which takes an executable and an explicit list of handles. There are no Unix signals either; Zircon uses object signals and an observer pattern, so asynchronous events arrive on a port you chose to wait on instead of interrupting whatever you were doing.

And a fresh process has nothing. No filesystem, no network, no clock, no parent. It holds exactly the handles its creator passed in, and it cannot reach anything else, because there is nothing ambient to reach for.

Calling Zircon a microkernel is half right. It is far smaller than Linux and pushes drivers and filesystems into user space, but it is not a minimal microkernel in the seL4 sense. The project’s own documentation describes it as a kernel plus a set of core userspace services, and the syscall surface is considerably wider than a purist would accept.

flowchart TB subgraph US["User space"] direction TB APP["Components
Chromium, Cast, session UI"] SVC["System services
filesystems, netstack, package resolver"] DRV["Drivers
driver hosts, one process per group"] end KRN["Zircon
threads · virtual memory · IPC · scheduling"] HW["Hardware"] APP --> SVC SVC --> DRV US --> KRN KRN --> HW


Everything is a component

Unix says everything is a file. Fuchsia says everything is a component, and means it more literally than the slogan suggests.

A component is a program plus a manifest. The manifest, a .cml file, declares what the component needs, what it provides, and who it hands things to. Components form a tree: each one can have children, and a component together with its children is a realm. A component’s position in that tree is its moniker, which looks like a path and works like one.

Capabilities move along the edges of that tree in three ways. A component can use a capability it needs, a parent can offer one to a child, and a child can expose one to its parent. There is no fourth option and no registry to go shopping in. If nobody routed a capability to you, it does not exist as far as you are concerned.

{
    include: [ "syslog/client.shard.cml" ],
    program: {
        runner: "elf",
        binary: "bin/echo_server",
    },
    capabilities: [
        { protocol: "fuchsia.examples.Echo" },
    ],
    expose: [
        {
            protocol: "fuchsia.examples.Echo",
            from: "self",
        },
    ],
}

At runtime, the capabilities routed to a component become its namespace. This is what replaces the global filesystem: what looks like a POSIX directory tree to the program is a private view assembled from what its parent offered, typically its own read-only package contents, some private storage, and a handful of protocols.

flowchart TD CORE["core
(parent realm)"] SRV["echo_server"] CLI["echo_client"] SRV -- "expose fuchsia.examples.Echo" --> CORE CORE -- "offer fuchsia.examples.Echo" --> CLI CORE -- "offer fuchsia.logger.LogSink" --> SRV CORE -- "offer fuchsia.logger.LogSink" --> CLI

Two more pieces make the model extensible. A runner is the thing that actually executes a component, and it is itself a capability: the ELF runner runs native binaries, other runners host other runtimes. A resolver turns a component URL into a manifest and a set of blobs. Both are replaceable, which is how an entire foreign runtime can be dropped into the system later without special-casing it in the kernel. Hold that thought for the Starnix section.

Reading manifests for the first time, my honest reaction was that this is a lot of ceremony to start a program. It is. What you get for it is that the dependency graph of the whole system is written down in files, checked by the build, and enforced at runtime by the OS rather than by discipline.


FIDL, and why the seams matter

If most of the operating system lives in user space, then most of the operating system is IPC. Filesystem access is IPC. Talking to a driver is IPC. So the interface language stops being a convenience and becomes the actual system ABI.

FIDL is that language. You declare a protocol, the compiler generates bindings for C, C++, Rust, Dart, and Go, and the messages ride on Zircon channels. The kernel knows nothing about FIDL; it moves bytes and handles.

library fuchsia.examples;

@discoverable
protocol Echo {
    EchoString(struct {
        value string:64;
    }) -> (struct {
        response string:64;
    });
};

The wire format is deliberately boring: little-endian, natively aligned, fixed layout, no compression, no pointer patching. You can read a message in place without allocating. For a filesystem server answering thousands of requests a second, that determinism is the point.

The part I find genuinely interesting is versioning. Components are built against a target API level, and the resulting package carries an ABI revision in its metadata, which you can see with ffx package far list on a meta.far. The platform knows which contract a given binary expects. That is the mechanism behind the updatable principle: if the platform can tell what an old component was compiled against, the platform can keep honouring it while everything else moves.

Whether that holds up over a decade is unproven. But it is a real design answer to a real problem, which is more than “recompile everything” ever was.


Packages and drivers

A Fuchsia package is not a file. It is a meta.far archive holding the package’s identity and a content listing, plus a set of blobs, and every blob is named by its Fuchsia Merkle Root: its content hash. Identical files across different packages are stored exactly once. A package resolver fetches what a component URL points at, and the package server acts as the root of trust.

The useful property here is hermeticity. Nothing resolves by name at runtime and gets whatever happened to be installed. Subpackages extend this: a package records the hashes of its dependencies, so a dependency cannot change without changing the parent’s hash too. Dependency hell is closed off at the naming layer instead of being patched over by a resolver.

Drivers get the same treatment one level down. Fuchsia drivers are shared libraries loaded into driver host processes in user space under a driver_manager component, and they are more restricted than a normal process: a driver cannot reach the filesystem or enumerate arbitrary devices. When a driver crashes, a driver host crashes, and the driver manager deals with it. The machine does not.

Drivers talk to the framework, to each other, and to the rest of the system over FIDL. Co-located drivers get a driver runtime that mimics the Zircon channel and port primitives in-process, so two drivers in the same host can talk without crossing into the kernel.

The driver ABI is where the vision and the current state diverge most. A stable driver ABI has been a goal since the first driver framework and is still not delivered as of 2023; DFv2 is explicitly described as evolving and not yet ABI stable. The promise that a driver compiled once keeps working across platform versions is still a promise.

Google Nest Hub The Nest Hub, which has now been through an entire kernel replacement without asking anyone. Photo: Graceoftheshire, CC BY-SA 4.0

This is what made the Nest Hub migration possible at all. Google did not push a new firmware image and hope. It pushed packages, into a system where the graphics stack, the Cast runtime, and the drivers are separately resolvable units. Whatever else you think of Fuchsia, that migration is the strongest evidence the model does something.


Starnix, the pragmatic part

Here is the problem with a clean-room OS: nobody has anything that runs on it. Android applications ship native code compiled against Linux. You cannot ask the world to recompile.

Starnix is Fuchsia’s answer, proposed in RFC-0082 and worked on since 2021. It is a runner that implements the Linux UAPI, the syscall interface Linux binaries expect, and translates it into Zircon operations. Unmodified Linux binaries run as Fuchsia components. There is no virtual machine and no emulated hardware in the path.

The test targets were chosen the way you would choose them if you were serious: low-level binaries from the Android source tree, and cases from the Linux Test Project. By 2022 a Starnix shell was available in Fuchsia workstation builds, and it was not a generic Linux environment but a small Android distribution running on top of Fuchsia, reachable over adb like any other Android device. Around the same time the device/google/fuchsia directory was deleted from AOSP, which read to a lot of people as one approach ending and another beginning.

The Windows comparison is the right one. WSL1 did the same thing in the other direction: implement the Linux syscall interface on a foreign kernel rather than ship a VM. It was good enough for an enormous amount of real work and eventually lost to WSL2’s actual Linux kernel. The Fuchsia team studied both WSL1’s wins and its failure modes before starting.

I do not know whether Starnix is a transition strategy or a permanent fixture, and I suspect the answer is whichever one keeps the project funded.


The parts that did not work out

A fair writeup of Fuchsia in 2023 has to include the retreats, because there have been several.

Flutter was supposed to be the application story. The 2017 UI was Flutter, the early SDK was Dart-first, and the coverage assumed Fuchsia and Flutter were a package deal. Then RFC-0176 landed in 2022: new Dart programs are not allowed in the Fuchsia source tree, existing ones live on an allowlist, and anything new needs an exemption. The stated reasons were toolchain tradeoffs around binary size, performance, and startup latency, plus the cost of keeping the Dart runtime updated in-tree. Dart did not get deleted, but the Flutter-everywhere narrative quietly stopped being true.

The graphics stack turned over too. Scenic’s original Gfx API modelled the screen as a 3D scene, complete with the volumetric soft shadows Escher was built for, and it turned out every real client, Flutter and Chromium and the session shell, was doing extra work to flatten a 3D scene back into the 2D product it actually was. Flatland replaced it with a 2D composition API that maps onto display controller hardware, and Gfx is deprecated.

Then January’s layoffs, and then July’s decision to drop the Assistant speakers. Whether that was the Amlogic chip in those speakers failing Fuchsia’s CPU requirements or a narrowing of scope after the cuts, the outcome is the same: after seven years Fuchsia runs on smart displays, and the list is not growing this year.

There is also a cost nobody has escaped. Moving drivers and filesystems into user space means IPC on paths where Linux does a function call. Fuchsia’s answers are good ones, such as the in-process driver runtime that keeps co-located drivers out of the kernel, but they are answers to a self-inflicted cost.


What I would steal from it

I do not expect Fuchsia to replace Android. I am not sure Google expects it either. But I have stopped reading it as a product bet, because the ideas in it are portable and several of them are things I want in systems I actually work on.

Capabilities instead of ambient authority is the big one, and the industry has been converging on it from the other side for a decade. Every container runtime, every seccomp filter, every IAM policy is an attempt to claw back authority that was granted by default. Fuchsia’s version is what it looks like when you start from zero instead.

Then the manifests. A system where every dependency between components is declared in a file, verified by the build, and enforced by the OS is a system where you can answer “what can this thing reach” by reading, not by testing. I have spent more hours than I want to admit answering that question the hard way.

And content-addressed, hermetic packaging, which is the same idea as a lockfile and a container digest, pushed all the way down to the drivers.

The demo Fuchsia has already delivered is smaller than the one it promised, and I think it is the more interesting one anyway. An operating system replaced itself underneath a shipping consumer product, over the air, and the users never found out. Seven years for that is either a long time or a bargain, depending on whether anything else ever gets to inherit it.


References