Files
playbook/antigravity-awesome-skills/skills/apple-container/references/concepts.md
T
2026-07-10 16:04:20 +00:00

18 KiB

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 under container image), but container is a distinct tool with a different runtime model. Do not assume Docker command paths, flags, defaults, or daemon behavior carry over (there is no container images/push/pull top-level command) — verify in references/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 helperone 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 targetcontainer 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-vmnet helper.

  • container system start creates a vmnet network named default, typically on CIDR 192.168.64.0/24 (gateway 192.168.64.1). Containers attach to default unless --network names 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 ls or container 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.test
    

    DNS domain create/delete edits the host resolver configuration, so it must run as administrator (sudo). The dns.domain property itself is set in config.toml, not via a CLI setter. See references/configuration.md for the dns commands and the dns.domain property.

  • 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-images helper, which exposes the image-management API. Manage it with the images command group and disk usage with container system df.
  • Registries: image references that omit a host default to docker.io, configurable via the registry.domain property (see references/configuration.md). Prefer fully-qualified references (docker.io/library/alpine, not bare alpine) 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/) holds content/, containers/, images, kernels/, networks/, volumes/, etc. — see the data-locations table in references/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 install

vmnet path bug (macOS 26): network creation fails if the container helper binaries live under ~/Documents or ~/Desktop. Use make install (runs from /usr/local), or keep the project elsewhere (e.g. ~/projects/container) when running the bin/libexec build 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 → ContainerClientcontainer-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 16g but 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 default network only, container network/--network unavailable, and a network-creation race that can cut containers off.

⚠️ Requires 1.0.0+. The container machine group was added in 1.0.0 (not in 0.7.1). Check container machine --help on 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, or none. Changes via container machine set apply after the next stop/start.
  • Nested virtualization (--virtualization + a custom CONFIG_KVM=y kernel 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/init works. On first boot container runs a built-in setup script to provision the mapped user; override it with an executable /etc/machine/create-user.sh in the image (runs once as root with CONTAINER_UID, CONTAINER_GID, CONTAINER_USER, CONTAINER_HOME, CONTAINER_MACHINE_ID set).

See references/commands.md for the full machine subcommand/flag matrix.