[Canonical HTML page](https://githits.com/blog/apple-container-vs-docker-desktop/)

[Back to blog](/blog.md)

![Apple container and Docker Desktop architecture paths compared in the GitHits dark wireframe style](/_astro/apple-container-vs-docker-desktop.rCVPWdxc_Z1rltRk.webp)

July 7, 2026 · 6 min read

# Apple container vs Docker Desktop: What the Source Says

A source-backed comparison of Apple container and Docker Desktop on macOS, using GitHits to trace every architecture claim to docs and code.

Apple shipped [`container`](https://github.com/apple/container) this year, an open-source tool for running Linux containers on Apple silicon. It is [written in Swift and built on Apple’s Containerization package](https://github.com/apple/container/blob/3ad6e9b8ba91418f033f8f05567022cf647b1f2c/Package.swift#L28-L57), which drives a [`Virtualization.framework` backed VMM](https://github.com/apple/containerization/blob/bd709ba8cb6515f013fa3d62343339642efb42e5/Sources/Containerization/ContainerManager.swift#L39-L59) on macOS. It runs the same OCI images you already build. So on the surface it looks like Docker Desktop with a new logo, and most of the coverage treated it that way.

It isn’t. The two tools draw the virtualization boundary in completely different places, and that one decision ripples out into isolation, memory behavior, and which workflows feel native. We wanted the difference grounded in the implementation, not the keynote, so we explored both projects with GitHits and traced every architectural claim back to source across four repositories.

Here is the whole thing in one sentence. Apple `container` boots a separate lightweight VM for each container, each with its own kernel. Docker Desktop runs a single Linux VM and packs the daemon and every container inside it, sharing one kernel. Everything else in this post follows from that.

**apple/container vs Docker Desktop isolation on macOS**

Both systems consume standard OCI images. apple/container runs its control plane on the macOS host and gives each container its own lightweight VM and kernel. Docker Desktop runs the Docker daemon, containerd, runc, containers, and shared Linux kernel inside one Docker-managed Linux VM.

GitHits traced the Desktop VM boundary to Docker docs and the engine/runtime chain to moby source.

| Question       | Apple container                                                        | Docker Desktop on macOS                                       |
| -------------- | ---------------------------------------------------------------------- | ------------------------------------------------------------- |
| Image format   | Consumes and produces OCI-compatible images                            | Runs OCI images through Docker Engine and containerd          |
| macOS VM model | One lightweight VM per container                                       | One Docker-managed Linux VM for the daemon and all containers |
| Kernel sharing | Each container has its own guest kernel in its own VM                  | Containers share the kernel inside the Docker Desktop VM      |
| Control plane  | `container-apiserver` runs as a host launch agent                      | The Docker daemon runs inside the Docker Desktop Linux VM     |
| Runtime path   | `container-apiserver` launches `container-runtime-linux` per container | `dockerd` drives containerd and the default runc v2 shim      |

## Research Prompt

Here is the prompt we handed a GitHits-enabled agent:

```text
Compare apple/container with Docker Desktop on macOS. Use
GitHits to inspect apple/container, apple/containerization,
docker/docs, and moby/moby. Verify the isolation model, runtime
chain, OCI image compatibility, and performance/resource
tradeoffs from source or official docs. Cite exact repository
permalinks for every architectural claim, and call out which
claims come from Docker Desktop docs versus moby source.
```

Everything below links to the exact source it came from. That was the point of the exercise: no claim survives unless the code or the docs back it.

## Evidence

Apple’s own `docs/technical-overview.md` says it plainly. The conventional macOS setup runs one Linux VM hosting many containers; `container` instead spins up a [lightweight VM for each container](https://github.com/apple/container/blob/3ad6e9b8ba91418f033f8f05567022cf647b1f2c/docs/technical-overview.md#L24-L39) you create. It keeps `container-apiserver` on the host and has it launch a [per-container `container-runtime-linux` helper](https://github.com/apple/container/blob/3ad6e9b8ba91418f033f8f05567022cf647b1f2c/docs/technical-overview.md#L46-L54).

The code agrees with the prose, which is not always a given. [`APIServer+Start.swift`](https://github.com/apple/container/blob/3ad6e9b8ba91418f033f8f05567022cf647b1f2c/Sources/APIServer/APIServer%2BStart.swift#L51-L90) wires up the container, network, kernel, volume, and disk-usage services. [`RuntimeLinuxHelper+Start.swift`](https://github.com/apple/container/blob/3ad6e9b8ba91418f033f8f05567022cf647b1f2c/Sources/Plugins/RuntimeLinux/RuntimeLinuxHelper%2BStart.swift#L28-L84) registers the launchd label `com.apple.container.runtime.container-runtime-linux` and starts a runtime service scoped to exactly one container.

Go one layer down into Containerization and the per-container claim stops being marketing. `ContainerManager` constructs a `VZVirtualMachineManager`, a [`Virtualization.framework` backed VMM](https://github.com/apple/containerization/blob/bd709ba8cb6515f013fa3d62343339642efb42e5/Sources/Containerization/ContainerManager.swift#L39-L59). [`LinuxContainer`](https://github.com/apple/containerization/blob/bd709ba8cb6515f013fa3d62343339642efb42e5/Sources/Containerization/LinuxContainer.swift#L29-L70) is the lifecycle type for a Linux container running inside a VM, and [`VZVirtualMachineInstance`](https://github.com/apple/containerization/blob/bd709ba8cb6515f013fa3d62343339642efb42e5/Sources/Containerization/VZVirtualMachineInstance.swift#L28-L83) wraps a `VZVirtualMachine` with its own CPU, memory, kernel, root filesystem, mounts, and network interfaces. The kernel really is a property of each instance. It is not a figure of speech.

Docker Desktop needs two sources instead of one, and the reason is itself informative: the macOS VM boundary is a Docker Desktop feature, not part of the open engine. The docs put the [Docker daemon and containers in a lightweight Linux VM](https://github.com/docker/docs/blob/00124c06a0496cb9b3d923685ac843a0849981cb/content/manuals/desktop/setup/install/mac-permission-requirements.md#L104-L112), and the security FAQ describes containers running in a [customized Linux VM](https://github.com/docker/docs/blob/00124c06a0496cb9b3d923685ac843a0849981cb/content/manuals/security/faqs/containers.md#L12-L23). Inside that VM, moby source shows the engine path you would expect: the daemon builds a [`libcontainerd` client](https://github.com/moby/moby/blob/01093f42abdba6f782f0ba0d17938072cb2311ba/daemon/daemon.go#L1345-L1350), and the Unix runtime config sets the default runtime to [`runc` via the `io.containerd.runc.v2` shim, wired as `plugins.RuntimeRuncV2`](https://github.com/moby/moby/blob/01093f42abdba6f782f0ba0d17938072cb2311ba/daemon/runtime_unix.go#L29-L83). Two claims from Docker’s docs, one from moby’s source, and they line up.

## What Changes

The isolation boundary is the whole ballgame.

Apple `container` wraps a VM around each individual workload. If a container gets popped, the blast radius is one guest kernel, and the next thing over is a different VM entirely. That is a genuinely nice property for the messier corners of modern development: coding-agent sandboxes running who-knows-what, generated build steps you did not write, security experiments you would rather not run next to your real work. When you need harder separation, you already have it. There is no shared kernel to escape into.

Docker Desktop draws the boundary around the environment as a whole. Every container shares the Desktop VM and its one guest kernel, with the macOS host sitting outside. For a dense Compose stack, a web app plus a database plus a queue plus a couple of sidecars, that shared VM is the sane default, and frankly it is what a decade of tooling already assumes. Cross-container networking just works because everything lives in the same place.

Resources push back in the other direction, and this is where per-container VMs stop being free. The overview is candid about a macOS memory-ballooning limitation: pages freed inside a guest are [not always returned to the host](https://github.com/apple/container/blob/3ad6e9b8ba91418f033f8f05567022cf647b1f2c/docs/technical-overview.md#L55-L59). Run twenty little services this way and you feel it. One shared VM is simply easier on your machine when the container count climbs, which is the tradeoff you are accepting in exchange for a stronger boundary.

## Practical Choice

The decision is less “which tool is better” and more “which boundary do you actually need.”

Reach for Apple `container` when the boundary is the point: agent sandboxes, tests for generated code, untrusted build scripts, or any Linux tool you would rather quarantine from the rest of your machine. The per-workload VM is doing real work for you there.

Stay on Docker Desktop when the workflow is the point: existing Dockerfiles and Compose files, onboarding a teammate this afternoon, matching CI, ordinary multi-service development. The ecosystem gravity is real and there is no prize for fighting it.

The two paths, side by side:

```text
OCI image
  ├─ apple/container
  │    CLI -> host apiserver -> per-container runtime
  │       -> one VM per container
  │
  └─ Docker Desktop
       docker CLI -> Docker-managed Linux VM -> dockerd
          -> containerd -> runc
          -> containers sharing one guest kernel
```

One caveat worth stating loudly. On Linux, Docker runs containers directly on the host kernel with no Desktop VM in the picture at all. This whole comparison is Docker Desktop on macOS against Apple `container` on macOS. Move to a Linux host and the terms change.

## Summary

Same images, different boundary. Apple `container` gives every container its own VM and its own kernel; Docker Desktop shares one of each across the lot. That makes Apple `container` the better pick when per-workload isolation is what you are after, and Docker Desktop the better pick when ecosystem and workflow are. Apple did not ship a Docker Desktop clone with a nicer CLI, and Docker Desktop did not become obsolete the day Apple shipped a runtime. They optimize for different boundaries. Pick the one that matches the work in front of you.

[Product](/blog/tag/product.md) · [Agents](/blog/tag/agents.md) · [Open Source](/blog/tag/open-source.md)

Nathan Burg — Co-founder, CPO

Keep exploring

* [The Solution](/the-solution.md)
* [Documentation](https://docs.githits.com/)
* [Changelog](https://docs.githits.com/changelog/overview)

Get started

## GitHits in one command

Install GitHits or refresh an existing setup with the same command.

`npx -y githits@latest init`
