How to Use Container Machine for VM-Based Workflows with Apple Container
Apple Container provides a container machine abstraction that converts OCI images into persistent, lightweight Linux VMs on macOS, automatically mounting your macOS home directory and preserving state across restarts.
The apple/container repository implements a unique virtualization layer that bridges traditional OCI containers with full Linux virtual machines. This container machine enables you to run long-lived Linux services, develop with native macOS editors while building inside Linux, and even experiment with nested virtualization—all from a standard container image.
What is a Container Machine?
A container machine is a persistent VM managed by the Apple Container runtime that boots from any OCI image containing an init system (/sbin/init). Unlike ephemeral containers, these machines maintain their filesystem across stops and starts, while remaining fully integrated with macOS.
Key characteristics include:
- Automatic home directory mounting – Your macOS
$HOMEappears inside the VM at/Users/<username> - Persistent state – The VM filesystem survives reboots and is stored in a dedicated machine directory
- Nested virtualization support – Available on Apple Silicon M3+ with macOS 15+ when using a kernel built with
CONFIG_KVM=y - Resource flexibility – CPU count, memory limits, and kernel paths are configurable via the
MachineConfigAPI
Core Architecture
The container machine implementation spans several key components in the codebase:
MachineCreate.swift– The CLI entry point inSources/ContainerCommands/Machine/MachineCreate.swiftthat parses flags and invokes the Machine ServiceMachineConfig– The persistent data model inSources/ContainerPersistence/MachineConfig.swiftthat stores CPU count, memory size, home-mount mode, and kernel paths as a Codable structureMachinesService– The server-side logic inSources/Services/MachineAPIService/Server/MachinesService.swifthandling create, inspect, stop, and remove operationsRuntimeService– The low-level runtime that launches VMs using Apple's virtualization framework
When you execute container machine create, the CLI calls MachineClient.machineConfigFromFlags to resolve image layers and build a boot configuration, which the Machine Service persists to boot-config.json before booting the VM.
Creating and Managing Machines
Quick Start
Create a persistent Linux environment from any OCI image with an init system:
# Create a machine named 'dev' from Alpine
container machine create alpine:latest --name dev
# Open an interactive shell (matches your macOS user)
container machine run -n dev
# Run a single command to verify home directory mounting
container machine run -n dev -- pwd
Set a default machine to omit the -n flag in subsequent commands:
container machine set-default dev
container machine run # Operates on 'dev' by default
Resource Configuration
Modify VM resources using the set command. Changes persist in MachineConfig and apply after the next stop/start cycle:
# Allocate 4 CPUs and 8GB RAM
container machine set -n dev cpus=4 memory=8G
# Restart to apply changes
container machine stop dev
container machine run -n dev -- nproc # Verifies 4 CPUs
The configuration is validated via MachineConfig.validateKernelPath when specifying custom kernels, and stored in the machine's boot-config.json.
Advanced Configuration
Enabling Nested Virtualization
For workloads requiring KVM (such as running nested VMs), enable virtualization support when creating the machine:
# Requires Apple Silicon M3+, macOS 15+, and a kernel with CONFIG_KVM=y
container machine create \
--virtualization \
--kernel /path/to/vmlinux-kvm \
--name kvm-dev \
alpine:latest
# Verify KVM device exposure
container machine run -n kvm-dev -- ls -l /dev/kvm
This configuration sets the nested virtualization flag in MachineConfig and exposes /dev/kvm inside the guest.
Building Custom Machine Images
Create OCI images with systemd for long-running services:
FROM ubuntu:24.04
ENV container container
RUN apt-get update && \
apt-get install -y dbus systemd openssh-server && \
apt-get clean && rm -rf /var/lib/apt/lists/* && \
yes | unminimize
RUN >/etc/machine-id && >/var/lib/dbus/machine-id
RUN systemctl set-default multi-user.target
Build and deploy the custom image:
docker build -t local/ubuntu-machine:latest .
container machine create local/ubuntu-machine:latest --name ubuntu
Lifecycle Management
Manage machine states through the CLI:
# List all machines
container machine ls
# View detailed configuration JSON
container machine inspect dev
# Stop the VM (preserves state)
container machine stop dev
# Delete VM and persistent storage
container machine rm dev
The inspect command returns the decoded MachineConfig including current resource allocations and kernel paths.
Summary
- Container machines transform OCI images into persistent Linux VMs with seamless macOS home directory integration
- Configuration persists in
boot-config.jsonvia theMachineConfigCodable model, storing CPU, memory, and virtualization flags - The Machine Service in
MachinesService.swifthandles orchestration while the Runtime Service manages actual VM execution - Nested virtualization requires Apple Silicon M3+, macOS 15+, and a custom kernel with
CONFIG_KVM=y - All machine state survives stops and starts, making them suitable for systemd-based workflows
Frequently Asked Questions
What is the difference between a container machine and a standard container?
A container machine is a persistent virtual machine that boots from an OCI image, whereas standard containers share the host kernel and are typically ephemeral. Machines maintain their filesystem across restarts, support init systems like systemd, and provide full Linux kernel isolation through Apple's virtualization framework.
Can I use any Linux OCI image as a container machine?
Any OCI image containing /sbin/init or an alternative init system can function as a machine image. Minimal images without an init process will not boot properly since the VM expects a standard Linux initialization sequence.
Where is the machine configuration stored?
Each machine's configuration is persisted as JSON in a boot-config.json file within the machine's storage directory. This file stores the MachineConfig values including CPU count, memory allocation, home-mount settings, and optional kernel paths, encoded via Swift's Codable protocol.
How does nested virtualization work with container machines?
Nested virtualization exposes the host's ARM virtualization extensions to the guest VM by setting the virtualization flag in MachineConfig. This requires Apple Silicon M3 or later running macOS 15+, along with a Linux kernel compiled with CONFIG_KVM=y. The feature allows running additional hypervisors like QEMU or firecracker inside the container machine.
Have a question about this repo?
These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →