container — Concepts & Architecture
Mental model for Apple's container: the one-VM-per-container design, the process
topology, platform requirements, networking, images/storage, and the security/isolation
model. Read this to explain how or why something works, or when a Docker mental model
gives the wrong answer. For concrete flags see references/commands.md; for defaults,
config.toml, and the machine settings see references/configuration.md; for end-to-end
recipes see references/workflows.md.
Sources: Apple's docs/technical-overview.md, README.md, docs/container-machine.md,
SECURITY.md, and BUILDING.md, as of the 1.0.0 release. For behavior on a specific
version, open the matching tag on the
release page.
Not Docker. The CLI is deliberately Docker-like (
container run,container build, image ops undercontainer image), butcontaineris a distinct tool with a different runtime model. Do not assume Docker command paths, flags, defaults, or daemon behavior carry over (there is nocontainer images/push/pulltop-level command) — verify inreferences/commands.md.
1. What container is — one lightweight VM per container
container builds and runs standard OCI/Linux containers on Apple-silicon Macs. It's
written in Swift and built on Apple's open-source
Containerization package for low-level container,
image, and process management.
The defining design choice: each container you create runs inside its own dedicated lightweight Linux VM. This is the opposite of the usual macOS approach (Docker Desktop, Podman, Lima), where a single long-lived Linux VM hosts a daemon and all containers share that one kernel via namespaces.
Each per-container VM boots a minimal Linux — a small set of core utilities and dynamic
libraries plus a vminitd init — rather than a full userland. Consequences of the model:
| Property | What the per-container VM gives you |
|---|---|
| Security | Every container has the isolation of a full VM, not just kernel namespaces. The minimal guest shrinks the attack surface and resource use. |
| Privacy | Mount only the host data a given container needs into that container's VM. A shared VM forces you to mount everything up front so it can be re-mounted selectively; per-container VMs avoid that broad exposure. |
| Performance | Lower memory than a full VM, with boot times comparable to containers inside a shared VM (sub-second in practice). |
| Interop | Consumes and produces standard OCI images, so images move freely to/from Docker registries and other OCI tooling. |
There is no shared daemon like dockerd. Instead, per-user background services run under
launchd and a runtime helper is launched per container (see §2).
2. Architecture — CLI, API server, and helper services
You drive everything through the container CLI. The CLI uses a client library
(ContainerClient) that talks over XPC to container-apiserver and its helpers.
container CLI
└── ContainerClient library ──XPC──► container-apiserver (launchd launch agent)
├──► container-core-images ──► local content store
├──► container-network-vmnet ──► virtual network (vmnet)
├──► builder (buildkit) ──► image builds
└──► container-runtime-linux (one instance per container)
└── my-web-server (that container's Linux VM)| Component | Role | Lifecycle |
|---|---|---|
container CLI + ContainerClient |
Command entry point; talks to the apiserver over XPC. | Runs per invocation. |
container-apiserver |
launchd launch agent exposing the client APIs for container and network resources. Launches the helpers below. | Started by container system start, torn down by container system stop. |
container-core-images |
XPC helper exposing the image-management API; owns the local content store. | Started by the apiserver. |
container-network-vmnet |
XPC helper managing the virtual network via the vmnet framework; allocates container IPs. | Started by the apiserver. |
builder (buildkit) |
Utility VM that runs container build; communicates over gRPC. |
On demand; runs as a container named buildkit. |
container-runtime-linux |
Container runtime helper — one instance per container — exposing the management API for that specific container's VM. | One per running container. |
container system is the control plane for this topology: start/stop bring the
apiserver and helpers up/down, status health-checks them, and logs/df/kernel/dns/
property manage system-wide state. No container, image, or build command works until
container system start has run — connection errors almost always mean the services are
stopped. See references/configuration.md for the full container system reference.
launchd service labels (all under the com.apple.container. prefix) look like:
com.apple.container.apiserver
com.apple.container.container-core-images
com.apple.container.container-network-vmnet.default
com.apple.container.container-runtime-linux.<container-name>macOS frameworks container builds on: Virtualization (VMs + attached devices),
vmnet (virtual network), XPC (client↔service IPC), launchd (service management),
Keychain (registry credentials), and the unified logging system (logs, surfaced via
container system logs).
3. Requirements & platform constraints
| Requirement | Detail |
|---|---|
| Chip | Apple silicon only (M1 or later). Intel Macs are not supported. |
| macOS | macOS 26 is the supported target — container relies on new virtualization/networking features there. It runs on macOS 15 but with the limitations in §7. Maintainers typically will not address issues that can't be reproduced on macOS 26. |
| Install | Download the signed .pkg from the GitHub release page and run it (installs under /usr/local, admin password required). Then container system start. |
Some features are gated on macOS 26 because they depend on vmnet capabilities absent in
macOS 15 (see §4/§7). This gating is enforced at runtime: using a macOS-26-only feature (e.g.
container network create, or --network <name>) on macOS 15 errors rather than
degrading silently.
macOS 26 vs macOS 15 — feature differences
| Capability | macOS 26 | macOS 15 |
|---|---|---|
| Container-to-container traffic over the virtual network | Works | Not possible — vmnet only creates networks where attached containers are isolated from one another. |
Multiple / custom networks (container network group, --network <name>) |
Available | Unavailable — all containers attach to the single default vmnet network; container network … and --network error. |
| Container network creation timing | Robust | Network is created only when the first container starts; the network helper (which hands out IPs) and vmnet can disagree on the subnet, potentially cutting containers off. See "All networking fails on macOS 15" in the upstream troubleshooting.md. |
Build-from-source requirement differs slightly: building needs macOS 15 minimum, macOS 26 recommended, plus Xcode 26 as the active developer directory (see §6).
4. Networking model
Networking is provided by the macOS vmnet framework, managed by the
container-network-vmnethelper.container system startcreates a vmnet network nameddefault, typically on CIDR 192.168.64.0/24 (gateway 192.168.64.1). Containers attach todefaultunless--networknames another.Each container is a first-class network endpoint with its own dedicated IP on that network (a direct consequence of the per-container-VM model — there is no shared Docker bridge). The network helper allocates the IP; read it with
container lsorcontainer inspect <name>(.networks[].address).Reach a container by IP directly. For name-based access, register a local DNS domain (admin required) so unqualified container names resolve:
sudo container system dns create test # register domain "test" on the host resolver # make it the default suffix by editing ~/.config/container/config.toml: [dns] domain = "test" # (there is NO `property set` CLI — only `container system property list` to view values) # a container named my-web-server is then reachable as my-web-server.testDNS domain create/delete edits the host resolver configuration, so it must run as administrator (
sudo). Thedns.domainproperty itself is set inconfig.toml, not via a CLI setter. Seereferences/configuration.mdfor thednscommands and thedns.domainproperty.Reach a host service from a container: create a domain pointed at a host IP with
--localhost(e.g.sudo container system dns create host.container.internal --localhost 203.0.113.113). Note the macOS caveats: this disables Private Relay, and the packet-filter rule is dropped on restart.Publish ports to the Mac's loopback with
--publish [host-ip:]host-port:container-port [/protocol]. If a container is on multiple networks, published ports forward to the interface on the first network.Custom / isolated networks require macOS 26. Create with
container network create <name> [--subnet … --subnet-v6 …]. Networks are mutually isolated — a container on one network has no connectivity to containers on another. Default subnets for new networks come from the[network]config (network.subnet/subnetv6) or are auto-allocated non-overlapping; the system rejects overlapping custom subnets. Networks support IPv4 and IPv6. On macOS 15 this whole group is unavailable (§3).
5. Images & storage
- Standard OCI in, standard OCI out. Pull/run images from any OCI registry, and push images you build to any OCI registry; they run in any other OCI-compatible tool.
- The local content store (host-side image/content storage) is owned by the
container-core-imageshelper, which exposes the image-management API. Manage it with theimagescommand group and disk usage withcontainer system df. - Registries: image references that omit a host default to
docker.io, configurable via theregistry.domainproperty (seereferences/configuration.md). Prefer fully-qualified references (docker.io/library/alpine, not barealpine) when the source registry matters. Registry credentials are stored in the macOS Keychain. - Selective host data sharing. Only the data a given container needs is mounted into that container's VM — a privacy/isolation win over the shared-VM model where everything must be mounted into the one VM up front.
- The application data root (default
~/Library/Application Support/com.apple.container/) holdscontent/,containers/,images,kernels/,networks/,volumes/, etc. — see the data-locations table inreferences/configuration.md.
6. Security, isolation & building from source
Isolation model. Each container is a full VM, so isolation is VM-grade rather than
namespace-grade as in a shared-daemon runtime. The minimal guest (small core utility/library
set + vminitd) keeps each VM's attack surface and footprint small, and per-container host
mounts limit data exposure to exactly what each container needs.
Security reporting. Report vulnerabilities via the project's GitHub private vulnerability reporting, not public issues. Known/published CVEs may be filed as normal issues. These reports are not eligible for Apple Security Bounties.
Building from source (brief). Requires Apple silicon, macOS 15 min / 26 recommended, and Xcode 26 set as the active developer directory.
# Build + run tests in an isolated data dir
rm -rf test-data
make APP_ROOT=test-data all test integration
# Install binaries to /usr/local/bin and /usr/local/libexec (admin password)
make install
# Release build (better perf than debug)
BUILD_CONFIGURATION=release make all test integration
BUILD_CONFIGURATION=release make installvmnet path bug (macOS 26): network creation fails if the
containerhelper binaries live under~/Documentsor~/Desktop. Usemake install(runs from/usr/local), or keep the project elsewhere (e.g.~/projects/container) when running thebin/libexecbuild artifacts directly.
To develop against a local checkout of the Containerization package or container-builder-shim,
point the runtime config (~/.config/container/config.toml) at your local vminit/builder
image and restart the services — see BUILDING.md for the exact swift-package steps. Attach a
debugger to an XPC helper by exporting CONTAINER_DEBUG_LAUNCHD_LABEL=<launchd-label> before
container system start.
7. How it differs from Docker, when to prefer it, and known limits
container vs Docker / shared-VM on macOS
| Aspect | container (Apple) |
Docker / shared-VM on macOS |
|---|---|---|
| VM topology | One lightweight Linux VM per container | One big Linux VM shared by all containers |
| Isolation | Full-VM per container | Namespace isolation within one VM |
| Guest contents | Minimal core utils + libs + vminitd |
Full Linux userland + daemon |
| Control plane | CLI → ContainerClient → container-apiserver (launchd) + XPC helpers |
dockerd daemon inside the shared VM |
| Host data sharing | Mount only what each container needs | Mount everything into the shared VM up front |
| Networking | Per-container dedicated IP on vmnet (default ≈ 192.168.64.0/24) |
Shared VM networking / Docker bridge |
| Images | Standard OCI in/out | OCI |
| Platform | Apple silicon + macOS 26 (15 with limits) | Cross-platform |
Prefer container when: you're on Apple silicon and want VM-grade isolation per
container, minimal per-container footprint, per-container IPs, tight macOS integration
(launchd/Keychain/unified logging), and no always-on shared daemon. Prefer Docker/Podman
when: you need Intel-Mac support, features container hasn't implemented yet, or maximum
Docker-flag/ecosystem compatibility.
Known limitations
- Memory is not fully returned to macOS. The Virtualization framework has only partial
memory ballooning. A VM uses only what the app needs (start with
--memory 16gbut see ~2 GiB in Activity Monitor), but pages freed inside the guest are not relinquished to the host. Running many memory-heavy containers may require occasionally restarting them. - Young project. 1.0.0 is the first stable release; some containerization features common in
Docker are still unimplemented (check
container <group> --help). Expect standard semver going forward — breaking changes on major bumps, not patch releases. - macOS 15 networking limits (see §3): no container-to-container traffic, single
defaultnetwork only,container network/--networkunavailable, and a network-creation race that can cut containers off.
8. Container machines — a related but distinct concept
⚠️ Requires 1.0.0+. The
container machinegroup was added in 1.0.0 (not in 0.7.1). Checkcontainer machine --helpon your install.
container machine (alias m) is not the app-shaped container above: it's a persistent,
integrated Linux environment modeled after a distro rather than a single app. It runs the
image's init system (so you can register long-running services / a process supervisor),
and auto-maps your host username and home directory into the guest, so your repos and
dotfiles are available on both sides with no copy step.
| Trait | Container | Container machine |
|---|---|---|
| Modeled after | one application | a full Linux environment / distro |
| Init | minimal (vminitd) |
the image's own init (e.g. systemd) |
| User | as configured / root |
matches your host account; $HOME mounted in |
| Lifetime | ephemeral by task | persistent across stop/start |
container machine create alpine:latest --name dev
container machine run -n dev whoami # your host username, not root
container machine run -n dev # interactive shell; your $HOME is mounted in
container machine set-default dev # then drop -n
container machine set -n dev cpus=4 memory=8G # takes effect after next stop/start- Memory defaults to half of host memory; the home mount can be
rw(default),ro, ornone. Changes viacontainer machine setapply after the next stop/start. - Nested virtualization (
--virtualization+ a customCONFIG_KVM=ykernel via--kernel) needs Apple silicon M3+ and macOS 15+; the default kernel does not support it. - Bring your own image: any Linux image with
/sbin/initworks. On first bootcontainerruns a built-in setup script to provision the mapped user; override it with an executable/etc/machine/create-user.shin the image (runs once as root withCONTAINER_UID,CONTAINER_GID,CONTAINER_USER,CONTAINER_HOME,CONTAINER_MACHINE_IDset).
See references/commands.md for the full machine subcommand/flag matrix.