How to Manage Container Machines for Persistent Development Environments
Container machines in the Apple Container tool provide persistent, bootable Linux VMs that preserve state across sessions, mount your macOS home directory automatically, and support full-system services like systemd.
The apple/container repository provides a Swift-based utility for running OCI-compatible Linux containers as lightweight virtual machines on Apple Silicon Macs. When you manage container machines for persistent development environments, you create stable Linux workstations that survive reboots, maintain installed packages, and integrate seamlessly with your macOS file system and editors.
What Are Container Machines?
A container machine is a persistent, bootable Linux environment that runs as a lightweight VM on Apple Silicon. Unlike ephemeral containers, machines preserve their disk state, configuration, and running services across stops and restarts.
The architecture consists of three layers:
- OCI Image – Any OCI-compatible image containing
/sbin/initcan serve as a base. The image is fetched and stored, then instantiated as a VM disk. - Container Runtime – The Swift package Containerization provides low-level VM, networking, and storage primitives, creating a lightweight VM backed by the image’s filesystem.
- CLI Front-end – The
container machinecommands, implemented inSources/ContainerPlugin/, map user requests to runtime calls and handle JSON inspection and state persistence.
Key capabilities include:
- Boot on demand – The first
container machine runautomatically starts the VM if it is not running. - Preserve state – The VM’s disk image, configuration, and any changes survive across stops and restarts.
- Host integration – Your macOS user’s UID/GID and
$HOMEdirectory are automatically mounted inside the machine. - Full-system services – Because the image’s init system (such as
systemd) runs inside the VM, you can start long-running daemons like PostgreSQL or Redis.
Creating and Starting Your First Machine
To create a new machine, use the container machine create command. This allocates a VM disk and writes a JSON configuration file to ~/.container/machines/<id>.json, as implemented in Sources/ContainerPlugin/PluginFactory.swift.
# Create but do not start yet
container machine create alpine:latest --name dev
# Run an interactive shell (auto-boots if needed)
container machine run -n dev
Inside the shell, $HOME points to your macOS home directory, giving seamless access to source files from macOS editors.
To avoid typing the machine name repeatedly, set a default:
container machine set-default dev
container machine run # works on the default machine
The set-default command writes a symlink named default in the machines directory, handled by Sources/ContainerPlugin/LaunchPlist.swift.
Managing Machine Lifecycle and State
Machines persist their root filesystem in a qcow2-style image file inside your container data directory. When stopped, the VM’s memory is flushed, but the disk image remains untouched.
Stop a machine while preserving state:
container machine stop dev
The stop logic is implemented in Sources/ContainerPlugin/ServiceManager.swift, which sends a shutdown request to the VM and updates the configuration file.
Inspect machine details, including dynamic fields like IP address and PID:
container machine ls
container machine inspect dev | jq .
The inspect functionality reads the JSON config and queries the VM for runtime data, as defined in Sources/ContainerPlugin/PluginStateRoot.swift.
Configuring Resources and Defaults
Resize CPU and memory allocations using container machine set. Changes are persisted to the configuration file but only take effect after the next stop/start cycle.
container machine set -n dev cpus=4 memory=8G
container machine stop dev
container machine run -n dev -- nproc # prints 4
This resource management logic resides in Sources/ContainerPlugin/PluginConfig.swift.
Building Custom Images with Systemd Support
You can bring your own image to manage container machines for specific development stacks. Any OCI image containing /sbin/init can serve as a machine base.
Here is a Dockerfile for an Ubuntu 24.04 development machine with systemd:
FROM ubuntu:24.04
ENV container container
RUN apt-get update && apt-get install -y \
dbus systemd openssh-server net-tools iproute2 iputils-ping curl wget vim-tiny man sudo && \
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 instantiate the custom image:
container build -t local/ubuntu-machine:latest .
container machine create local/ubuntu-machine:latest --name ubuntu
container machine run -n ubuntu
Enabling Nested Virtualization
For advanced use cases, nested virtualization allows the VM to expose /dev/kvm to guest containers. This requires Apple Silicon M3 or later, macOS 15+, and a Linux kernel built with CONFIG_KVM=y.
Create a machine with a custom KVM-enabled kernel:
container machine create \
--virtualization \
--kernel /path/to/vmlinux-kvm \
--name kvm-dev \
alpine:latest
# Verify /dev/kvm is exposed inside
container machine run -n kvm-dev -- ls -l /dev/kvm
See docs/container-machine.md for complete details on nested virtualization requirements.
Summary
- Container machines are persistent Linux VMs that maintain state across reboots, implemented in the
apple/containerSwift-based tool. - State is stored in qcow2 disk images and JSON configuration files located in
~/.container/machines/. - The CLI commands (
create,run,stop,set,inspect) are implemented acrossPluginFactory.swift,ServiceManager.swift,PluginConfig.swift, andPluginStateRoot.swift. - Host integration automatically mounts your macOS
$HOMEand preserves UID/GID mappings. - Any OCI image with
/sbin/initcan serve as a machine base, enabling full systemd support and long-running daemons.
Frequently Asked Questions
Where is container machine state stored on macOS?
Machine state is stored in your user’s container data directory, typically ~/.container/machines/<id>.json for configuration metadata and a corresponding qcow2 disk image file for the root filesystem. These files persist across macOS reboots, allowing you to resume exactly where you left off.
Can I use any Docker image as a container machine base?
You can use any OCI-compatible image that includes /sbin/init as the entry point. Standard container images without an init system will not boot properly. For systemd-based distributions, ensure the image installs systemd and sets a default target, as shown in the Ubuntu Dockerfile example.
How do I enable nested virtualization for running VMs inside my container machine?
Nested virtualization requires Apple Silicon M3 or later, macOS 15 or later, and a Linux kernel compiled with CONFIG_KVM=y. Create the machine with the --virtualization and --kernel flags pointing to your KVM-enabled kernel binary. Once running, /dev/kvm will be available inside the guest for further virtualization.
Do resource changes apply immediately when I resize CPU or memory?
No. When you run container machine set to adjust CPUs or memory, the changes are written to the persistent configuration file but only take effect after you stop and restart the machine. The implementation in Sources/ContainerPlugin/PluginConfig.swift handles this persistence, while ServiceManager.swift applies the new limits on the next boot cycle.
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 →