One VM Per Container
A Swift CLI on macOS runs each Linux container in its own lightweight VM — that single decision reshapes the security model, the helper topology, and the macOS-version matrix.
Series in one paragraph
container is Apple's answer to "I want Linux containers that feel native on my Mac." What makes it worth your attention is not the CLI surface or the OCI compatibility — both competent, both unsurprising — but the architectural bet underneath: every container gets its own VM, not a slot in a shared VM. That decision cascades into a fault-isolated helper topology, a separate "container machine" abstraction, and a hard dependency on macOS 26 that you cannot negotiate around. Read the series and you'll know not just what container does, but why it does it that way — and where the model breaks.
This opening chapter sets the scene; the next four walk the architectural thesis, the runtime topology, the container-machine ergonomic, and the honest list of trade-offs.
The first time you run it
Picture Tuesday morning. You've just installed container on an M-series Mac. You open a terminal and type the command that starts everything:
container system start
The CLI doesn't ask you to choose between a Linux VM backend and a hypervisor framework. It doesn't ask you to allocate a memory pool. It just installs a recommended Kata Containers kernel the first time, then leaves you at a prompt. You run container run --rm alpine:latest echo hello, and the response comes back. Activity Monitor shows a new process called container-runtime-linux, a few hundred megabytes of memory, and a transient entry for the VM itself. That entry disappears the moment the container exits.
If you've used Docker Desktop on macOS, this experience is mildly disorienting. Docker Desktop owns a long-lived Linux VM that sits in your menu bar — a single qemu or VZLinux process you can see growing as you stack up containers. Stop and start one container, and you're reconfiguring slots inside a hotel. Here, the hotel doesn't exist. Each container checks in, gets a room, and checks out, and the rooms are built and demolished around them.
That's the experience. Now the architecture.
What Apple actually shipped
container is a Swift Package Manager project. The container CLI binary is a thin client; the work happens in a launchd-managed launch agent called container-apiserver. That daemon, in turn, spawns three categories of XPC helper when it boots:
container-core-images— image management and the local OCI content store.container-network-vmnet— the virtual network helper, talking to Apple'svmnet.framework.container-runtime-linux— one instance per container, owning that container's VM lifecycle.
That last point is the load-bearing sentence. Per the technical overview, "using the open source Containerization package, it runs a lightweight VM for each container that you create." The Containerization package — a sibling Swift project at apple/containerization — handles the Virtualization.framework plumbing. container orchestrates it.
Why this series, and why now
If you spend your day running Linux workloads on a Mac, you've already chosen a tool. You may use Docker Desktop, OrbStack, Podman Desktop, or a Colima + lima setup. Each of those runs one Linux VM on your behalf and stuffs containers into it as OS-level processes. The trade-off they all make is the same: amortize the cost of one VM across many containers, accept that all containers share a kernel, and treat isolation as a configuration problem.
container makes the opposite trade. It does not amortize the VM. It does not share kernels. It treats isolation as a structural property — you get your own kernel because you have your own VM — and asks you to accept higher per-container overhead in exchange. Whether that exchange is worth it depends on what you actually run, on which macOS, and under what threat model. The chapters ahead try to give you a defensible answer for each of those questions rather than a generic "yes, use it."
What you'll have after five chapters
- Chapter 1 — Why Apple rejected the shared-VM model, and what the per-container VM actually buys you in boot time, memory, and attack surface.
- Chapter 2 — How the runtime topology (apiserver + per-container XPC helpers) implements the per-container VM decision in Swift, and why fault isolation is the silent reason for the architecture.
- Chapter 3 — Container machines: the underappreciated Apple-specific abstraction that turns a throwaway container into a persistent Linux dev environment with your
$HOMEalready mounted. - Chapter 4 — The trade-off ledger: macOS 15 vs macOS 26, memory ballooning, the default capability set, and a decision matrix for picking between
container, OrbStack, and Docker Desktop.
The arc isn't "look at all the features." The arc is "follow one architectural decision through the system and discover what it costs, what it gives, and where it ends."
Where this article series stops being about features
You will notice that this opening chapter mentions almost no CLI flags. That's deliberate. The flag surface is well-covered in docs/command-reference.md. What is not well-covered — what most second-tier write-ups skip — is why the system is shaped the way it is, and where its shape stops helping you. The point of this series is to leave you holding the architectural map, not the cheat sheet.
There's one more thing worth saying before the next chapter. The Kata Containers static kernel is the default. Apple did not build its own kernel. That's an unusual choice for a first-party macOS project, and a deliberate one: kernel maintenance is a separate discipline, and the project would rather ride Kata's release cadence than ship a kernel that quietly rots. You'll see that choice echo through several places in the chapters that follow, and Chapter 4 returns to it as a trade-off worth being explicit about.
Next: the architectural thesis itself, and why the simplest reading of "one VM per container" gets the most important detail wrong.