--- name: apple-container-machine description: > Use this skill when working with Apple's `container machine` subcommand on Apple Silicon Macs — creating persistent Linux environments, building custom machine images, managing lifecycle, setting up VSCode Remote-SSH development, or configuring host integration (home-dir mount, SSH, DNS). Triggers on "container machine", "container m", "machine create", "machine run", "apple container machine", "container machine image", "m run", or any request involving Apple's container tooling for persistent Linux environments. --- # Apple Container Machine Skill ## What This Is `container machine` (alias `m`) is a subcommand of Apple's [container](https://github.com/apple/container) tool — an open-source CLI that runs Linux containers as lightweight virtual machines on Apple Silicon Macs (macOS 26+). Unlike standard `container run` which models an application container, `container machine` models a **persistent Linux workstation environment**: it runs the image's init system (`/sbin/init`), maps your macOS user account and home directory into the VM, and survives stop/start cycles. **Key differences from `container run`:** - Runs `/sbin/init` (supports `systemd`, `openrc`, etc.) for long-running services - Auto-maps host username, UID, GID, and `$HOME` into the VM - Persistent filesystem across stop/start — data survives - Resources (CPU, memory) configurable at create time and resizeable later - One machine per target distro — test the same app under Alpine, Ubuntu, Debian ## Prerequisites - Apple Silicon Mac (M1+) - macOS 26 recommended (macOS 15 has limitations: no container-to-container networking, no `container network` commands) - `container` tool installed and `container system start` has been run - Linux kernel installed (`container system kernel set --recommended`) ## Quickstart ```bash # Create a container machine from an OCI image container machine create alpine:3.22 --name dev # Run a command (boots automatically if stopped) container machine run -n dev whoami # your host username, not root container machine run -n dev pwd # your Mac home directory, mounted in # Open an interactive shell container machine run -n dev # login shell as your host user # List all machines container machine ls # Stop and delete container machine stop dev container machine rm dev ``` The alias `m` works everywhere `machine` does: `m ls`, `m run -n dev`, `m stop dev`. ## Container Machine Lifecycle ``` pull image → create → boot → run commands / services → stop → delete ``` ### Create ```bash container machine create --name [--cpus N] [--memory N] [--home-mount rw|ro|none] ``` The image must contain `/sbin/init`. See "Image Requirements" below. - `--no-boot` — create without booting - `--set-default` — also set as the default machine - `--cpus` — virtual CPUs (default: half of host count) - `--memory` — RAM (default: half of host memory, e.g., `8G`, `16G`) - `--home-mount` — home directory access: `rw` (default, read-write), `ro` (read-only), `none` (no mount) ### Boot A machine boots automatically on first `container machine run` or when `run` is invoked on a stopped machine. No separate boot command is needed. ### Run Commands ```bash container machine run -n [command...] # run a single command and exit container machine run -n # interactive login shell container machine run -n -- # pass args after -- container machine run -n --root # run as root instead of host user container machine run -n --detach # run in background ``` **Quoting limitation.** `container machine run` wraps the command in a shell layer that re-interprets quoting. Complex shell constructs — `&&` chaining, heredocs (`<<`), pipes to the command, mixed quote styles — are unreliable. Use simple single commands; for multi-step work, write a script on the mounted home dir and invoke it: ```bash # Reliable: simple command container machine run -n sdamin -- cat /etc/os-release # Unreliable: this will likely mangle quoting container machine run -n sdamin -- sh -c "systemctl status nginx && journalctl -u nginx -n 5" # Reliable workaround: source the script from the mounted home container machine run -n sdamin -- bash ~/projects/scripts/check.sh ``` Process flags: `-e KEY=value`, `--env-file`, `-u user`, `--uid`, `--gid`, `-w /workdir`, `-i` (interactive), `-t` (tty). ### Inspect ```bash container machine inspect # JSON detail container machine ls # table: name, IP, cpus, memory, disk, state container machine logs # container machine stdio logs container machine logs --boot # VM boot log ``` ### Stop ```bash container machine stop # graceful stop ``` ### Configure (resize resources) ```bash container machine set -n cpus=4 memory=8G container machine stop container machine run -n # restart for changes to take effect ``` ### Set Default ```bash container machine set-default # drop the -n flag for subsequent commands ``` ### Delete ```bash container machine stop container machine rm # removes machine AND persistent storage ``` If the deleted machine was the default, set a new one: `container machine set-default `. ## Image Requirements Container machines require OCI images that include `/sbin/init`. The machine runs the image's init system as PID 1 on boot. ### Ubuntu 24.04 + systemd (canonical example) This Dockerfile builds a machine image with systemd and common tools: ```dockerfile 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 RUN >/var/lib/dbus/machine-id RUN systemctl set-default multi-user.target RUN systemctl mask \ dev-hugepages.mount \ sys-fs-fuse-connections.mount \ systemd-update-utmp.service \ systemd-tmpfiles-setup.service \ console-getty.service RUN systemctl disable \ networkd-dispatcher.service RUN sed -i -e 's/^AcceptEnv LANG LC_\*$/#AcceptEnv LANG LC_*/' /etc/ssh/sshd_config ``` Build and create: ```bash container build -t local/ubuntu-machine:latest -f Dockerfile . container machine create local/ubuntu-machine:latest --name ubuntu ``` ### systemd on other distros The same pattern applies to any systemd-based distro (Debian, Fedora, Rocky Linux): - Set `ENV container container` - Install `dbus` and `systemd` - Create empty `/etc/machine-id` and `/var/lib/dbus/machine-id` - Set `multi-user.target` as default - Mask unnecessary services ### init systems other than systemd Any Linux image with `/sbin/init` works. For Alpine (busybox-init) or distros with OpenRC, the init system handles PID 1 responsibilities. ### First-boot provisioner By default, `container` runs a built-in setup script on first boot to create the user matching your host account. To use your own setup instead, add an executable script at `/etc/machine/create-user.sh` to the image. It runs once, as root, on first boot, with these environment variables: - `CONTAINER_GID` — group ID - `CONTAINER_HOME` — home directory path - `CONTAINER_MACHINE_ID` — machine UUID - `CONTAINER_UID` — user ID - `CONTAINER_USER` — username ## Commands Reference `m` is the alias for `machine` — both forms work everywhere. | Command | Description | |---------|-------------| | `container machine create ` | Create and boot a machine. Flags: `--name`, `--cpus`, `--memory`, `--home-mount`, `--no-boot`, `--set-default`, `--scheme`, `--progress` | | `container machine run` | Run a command or open a shell in a machine (boots if stopped). Flags: `-n `, `-d` (detach), `--root`, process flags (`-e`, `-u`, `-w`, `-i`, `-t`, `--gid`, `--uid`, `--env-file`) | | `container machine ls` | List machines. Flags: `--format json\|table`, `-q` | | `container machine inspect ` | JSON detail of a machine | | `container machine set ` | Update config. Flags: `-n `. Valid keys: `cpus`, `memory`, `home-mount`. Requires stop/start. | | `container machine set-default ` | Set the default machine | | `container machine stop ` | Stop a running machine | | `container machine logs ` | Show logs. Flags: `--boot`, `-f` (follow), `-n N` | | `container machine rm ` | Delete machine and persistent storage | ## Host Integration ### Home directory mount The container machine automatically mounts your macOS home directory inside the VM at `/Users/`. Your repos, dotfiles, and SSH keys are available from both macOS and the Linux environment. ```bash container machine create ... --home-mount rw # read-write (default) container machine create ... --home-mount ro # read-only container machine create ... --home-mount none # no mount ``` - The host user is automatically created in the machine with matching UID/GID - Edit on macOS with your native tools; compile and run in the Linux VM - Profilers, screenshot tools, and debuggers on macOS see the same files **Important: `$HOME` path mismatch.** The machine sets `$HOME` to `/home/` inside the VM, but the actual mounted home is at `/Users/`. Tools that resolve `~` relative to `$HOME` (SSH key lookup, config file discovery) may look in the wrong place. This most commonly breaks SSH identity file resolution. Workaround: ```bash # Symlink the VM home .ssh to the mounted macOS .ssh container machine run -n --root ln -s /Users//.ssh /home//.ssh ``` Or use absolute paths when referencing files on the mounted home. ### SSH agent forwarding Add an SSH config entry so the machine can access your SSH agent: ```bash cat >> ~/.ssh/config <.machine` resolves to the machine's IP: ```bash ping -c 1 ubuntu.machine ``` To remove the domain later: ```bash sudo container system dns delete machine ``` ### Passwordless sudo inside a machine Services started via `systemctl` inside the machine run as root by default. For interactive sudo access: ```bash container machine run -n sudo passwd $(whoami) # Enter a password, then use sudo normally in shells ``` ## VSCode Remote-SSH Development This workflow lets you use Visual Studio Code with the Remote-SSH extension to develop code stored on your Mac, compiled and run inside the container machine. ### Setup 1. Create a machine with systemd and SSH: ```dockerfile FROM ubuntu:24.04 ENV container container RUN apt-get update && \ apt-get install -y \ dbus systemd openssh-server sudo curl wget git && \ apt-get clean && \ rm -rf /var/lib/apt/lists/* RUN >/etc/machine-id RUN >/var/lib/dbus/machine-id RUN systemctl set-default multi-user.target RUN systemctl mask \ dev-hugepages.mount \ sys-fs-fuse-connections.mount \ systemd-update-utmp.service \ console-getty.service ``` 2. Build and create: ```bash container build -t ubuntu-machine:latest . container machine create --set-default --name ubuntu ubuntu-machine:latest ``` 3. Set up DNS: ```bash sudo container system dns create machine ``` 4. Add SSH config: ```bash cat >> ~/.ssh/config <` inside the machine — all your repos are available. ### Debugging Set breakpoints in VSCode, run the application inside the machine, and debug normally. The home-dir mount means compiled artifacts are visible to both the machine and macOS tools. ### Clean up ```bash container machine stop ubuntu container machine rm ubuntu container image rm ubuntu-machine:latest sudo container system dns delete machine # Remove the SSH config entry from ~/.ssh/config ``` ## Resource Configuration ### At create time ```bash container machine create alpine:3.22 --name dev --cpus 4 --memory 8G ``` - Default CPUs: half of host count - Default memory: half of host memory - Memory format: `512M`, `2G`, `8G`, etc. (1 MiB granularity) ### Post-creation resize ```bash container machine set -n dev cpus=8 memory=16G container machine stop dev container machine run -n dev -- nproc # verify: 8 ``` Changes only take effect after a stop/start cycle. ### Check current resources ```bash container machine inspect dev | jq '.cpus, .memoryInBytes' container machine ls # table shows CPUS and MEMORY ``` ## Multi-Distro Workflows Create one container machine per target distribution. Each machine mounts the same `$HOME`, so your repos and dotfiles are available everywhere: ```bash container machine create alpine:3.22 --name dev-alpine container machine create ubuntu:24.04 --name dev-ubuntu # uses /sbin/init from systemd container machine create debian:latest --name dev-debian ``` ```bash container machine run -n dev-alpine apk add build-base container machine run -n dev-ubuntu apt-get install build-essential container machine run -n dev-debian apt-get install build-essential ``` Each machine runs its own init system under its own Linux VM, fully isolated. Test your application under different distributions and init systems from the same source tree. ## Troubleshooting ### Machine fails to boot Ensure a Linux kernel is installed: ```bash container system kernel set --recommended ``` Check boot logs: ```bash container machine logs --boot ``` ### DNS not resolving (`.machine` unknown) Verify the DNS domain was created: ```bash cat /etc/resolver/machine sudo container system dns create machine ``` Verify the machine is running and has an IP: ```bash container machine ls ``` ### SSH connection refused Ensure SSH is installed and running inside the machine: ```bash container machine run -n sudo systemctl status ssh container machine run -n sudo systemctl enable --now ssh ``` On images without systemd, start sshd explicitly: ```bash container machine run -n sudo /usr/sbin/sshd ``` Check SSH config: ```bash ssh -v ubuntu.machine ``` ### Home-mount permission errors If the machine uses `--home-mount ro`, writes to `$HOME` will fail. Switch to `rw`: ```bash container machine set -n home-mount=rw container machine stop container machine run -n ``` ### macOS 15 limitations On macOS 15, the following do not work: - Container-to-container networking over the virtual network - `container network` commands - `--network` flag on `container run`/`container create` Container machines still work but are limited to host-only networking. ### Resource not updating after `container machine set` Changes to CPU/memory/home-mount only apply after a stop/start cycle: ```bash container machine set -n dev cpus=4 container machine stop dev # must run after set container machine run -n dev # boot with new config ``` ### Machine appears but is unreachable Rare vmnet subnet conflict on macOS 15. If your machine has no network access: 1. Stop the machine: `container machine stop ` 2. Stop the container system: `container system stop` 3. Start again: `container system start` 4. Boot the machine: `container machine run -n ` ## Common Pitfalls 1. **macOS 15 network isolation**: Container-to-container communication does not work on macOS 15. Only the host can reach the container's IP. 2. **No default kernel**: `container system start` prompts to install one automatically. If skipped, run `container system kernel set --recommended`. 3. **SSH host key changes**: After deleting and recreating a machine with the same DNS name, `ssh` will warn about host key change. Remove the old key from `~/.ssh/known_hosts`. 4. **Home-mount read-only**: `--home-mount ro` causes any write to `$HOME` to fail. Use `rw` unless you have a specific security reason. 5. **Forgetting to stop before delete**: `container machine rm` stops the machine first, but data loss is still permanent. 6. **Images without `/sbin/init`**: A container machine will not boot from a standard application image (e.g., `python:alpine`). Use images built with `/sbin/init`. 7. **Disk space**: Each machine has its own persistent disk. Monitor usage with `container system df`. 8. **Multiple machines sharing the same home directory**: Concurrent writes from multiple machines to the same files can cause conflicts. Use different working directories per machine. 9. **`$HOME` is not `/Users/`**: The machine sets `$HOME` to `/home/` but the mounted macOS home is at `/Users/`. SSH identity file paths in `~/.ssh/config` resolve relative to `$HOME` and will point to `/home//.ssh/` where keys don't exist. Symlink `/home//.ssh` to `/Users//.ssh` to fix. 10. **Shell quoting with `container machine run`**: Avoid `&&`, heredocs, and pipes in commands passed to `container machine run`. The command wrapper re-interprets quoting. Use single commands or invoke scripts from the mounted home directory instead. ## Sources - [Apple container GitHub repo](https://github.com/apple/container) - `docs/container-machine.md` — full container machine guide - `docs/command-reference.md` — Machine section with all flags - `docs/how-to.md` — container machines how-to - `examples/container-machine-vscode/README.md` — VSCode Remote-SSH walkthrough