Apple Container Skill
Operate Apple's container CLI as a native macOS Linux container runtime. This skill should guide the agent's choices, not just provide command syntax.
First Moves
Before doing real work, establish whether the host and runtime are usable:
sw_vers
uname -m
command -v container
container --version
container system status
- Require Apple silicon (
arm64). Current Apple docs support macOS 26+ for real use; macOS 15 has important networking limitations.
- Record the CLI and service versions. If the host is older than 1.2.0, recommend upgrading before diagnosing image-environment leakage, build-context failures, stalled published ports, or machine API timeouts; those are known fixed defects. Do not assume the CLI and running service are the same version.
- If the CLI is missing, prefer Apple's signed installer from the GitHub releases page. Homebrew can work, but if
container system start fails with missing plugins after a Homebrew install, upgrade/reinstall the formula.
- Start services with
container system start when status shows they are stopped. First start may prompt to install the recommended Linux kernel.
- Run a smoke test before blaming application code:
container run --rm docker.io/library/alpine:latest sh -lc 'uname -a; nslookup github.com'
For an unknown or flaky environment, run the bundled diagnostic:
bash skills/apple-container-skill/scripts/diagnose.sh
Choose The Right Runtime Shape
- Use
container run for disposable app containers, one-shot Linux commands, project dev shells, image smoke tests, and services whose state should live in bind mounts or named volumes.
- Use
container machine for a long-lived Linux workspace: repeated distro testing, system services, VS Code Remote SSH, a persistent root filesystem, or "edit on macOS, build inside Linux" loops.
- Do not describe machines as merely "persistent containers." A machine is a convenience wrapper around a container, a separate persistent root disk, and host integration. It maps the host user, forwards SSH agent support, and mounts the macOS home at
/Users/<user> while the Linux user's $HOME is /home/<user>.
- Plain OCI application images may fail in machine mode because machines need a bootable init system. Plain
alpine:3.22 can create, inspect, and stop but fail command execution if /sbin/openrc is absent. Build an OpenRC-capable Alpine image or a systemd-capable Ubuntu/Debian image for reliable container machine run.
- For scripted machine commands, prefer an option terminator:
container machine run -n dev -- whoami or container machine run -n dev -- /bin/sh -c 'whoami; pwd; echo "$HOME"'. Avoid -i in heredoc/non-interactive scripts because it can consume the rest of the script from stdin.
Machine Image Selection
When the user names a distro image, preserve the distro choice but choose the runtime shape correctly:
- For one-shot commands or app containers, use the requested image directly with
container run.
- For
container machine, treat the requested image as a base image unless it is already known to be machine-capable.
- Do not silently try to use plain
ubuntu, debian, or alpine app images as long-lived machines. Explain that machines boot an init system, then derive a machine image from the requested base.
- For Alpine machines, install both
openrc and openrc-init, add related user/network tools, set CMD ["/sbin/openrc-init"], then build a local *-machine image. Installing only openrc and using BusyBox /sbin/init can start and immediately shut down.
- For Ubuntu/Debian machines, add systemd, dbus, sudo, SSH/network tools as needed, set the systemd target, and build a local
*-machine image.
- If the user insists on the exact image without derivation, use
container run or warn that container machine may create but fail to boot or execute commands.
Safety Rules
Ask before:
- Running
sudo, installing, upgrading, uninstalling, or changing DNS resolver entries.
- Global cleanup:
container prune, container image prune, container volume prune, container network prune, or deleting all resources.
- Deleting named machines, volumes, images, or containers that were not created for the current task.
- Editing
~/.ssh/config, changing host firewall/VPN settings, or widening bind mounts beyond the project directory.
- Passing kernel arguments that disable or weaken security controls. Use
--kernel-arg only for a documented kernel-level requirement, not ordinary application configuration.
Prefer graceful stops (container stop, container machine stop) before forceful deletion or container kill.
Practical Defaults
Common project shell:
container run --rm -it -v "$PWD:/work" -w /work docker.io/library/ubuntu:24.04 bash
Build and run an image:
container build -t local/app:dev .
container run --rm -p 8080:8080 local/app:dev
Long-lived machine:
container machine create local/alpine-machine:latest --name dev --set-default --cpus 4 --memory 8G
# Run the first command from a real terminal so initial user setup has a host PTY.
container machine run -n dev -- /bin/sh -c 'whoami; pwd; echo "$HOME"'
container machine stop dev
Important Current Behaviors
container system property get, set, and clear were removed in 1.0. Use ~/.config/container/config.toml for defaults and container system property list only to inspect effective config.
- Apple's signed installer places the CLI at
/usr/local/bin/container; check that path directly when a fresh shell cannot find container.
container build may leave the BuildKit builder running. If a validation task must leave no runtime processes, inspect container builder status, then use container builder stop and container builder delete.
- Host-to-container traffic should usually use
-p/--publish; if it fails, check that the app listens on 0.0.0.0 inside the container.
- For Unix sockets, choose by direction: bind-mount (
-v) a socket that already exists on the host into the container; use --publish-socket host_path:container_path when the process in the container creates the socket and the host must reach it. For non-root clients, verify ownership and mode on both endpoints. Apple Container 1.1 fixed non-root socket-mount access.
- Apple Container 1.1 also fixed relative local paths for
container cp; on older versions, use absolute paths or upgrade rather than debugging a correct relative path.
- Apple Container 1.2 prevents an untrusted image's bare
ENV entry from implicitly copying a same-named host variable. Still make inheritance explicit: use -e NAME only when host passthrough is intended, and prefer KEY=value, an env file, or a secret mechanism for reproducible runs.
- Use repeatable
--kernel-arg key=value only for a kernel/security/debug requirement and verify the effective command line with cat /proc/cmdline. Do not use it for application settings or casually replace security defaults such as lsm=landlock.
- The 1.2.0 release does not expose
container run --stop-signal, despite earlier release notes mentioning it. Put STOPSIGNAL in the image for a reusable default or use container stop -s SIGNAL for an operator-selected signal.
- Treat named volumes as single-attachment unless the workflow has proven otherwise; do not assume the same named volume can be attached concurrently to multiple running containers.
- Container-to-host traffic does not use Docker's magic host alias. Configure a localhost DNS domain:
sudo container system dns create host.container.internal --localhost 203.0.113.113
This can disable Private Relay, and packet-filter rules may need recreation after reboot.
container network user-defined networks require macOS 26+. On macOS 15, container-to-container networking and multiple networks are limited.
- If networking worked and then fails after VPN or endpoint security changes, suspect vmnet/VPN routing before changing application code.
- A machine's first command may need a real host terminal while user setup completes. If a headless first run fails with
Operation not supported by device, retry the initialization from a PTY; subsequent non-interactive machine run ... -- command calls can be scripted. Do not add -i to a heredoc or unattended job.
References
- Read references/workflows.md for common development, build, network, registry, volume, and machine playbooks.
- Read references/troubleshooting.md when service startup, DNS, VPN, vmnet, builder, Rosetta, port publishing, or machine mode fails.
- Read references/commands.md for concise command coverage after you know which workflow you need.
1---2name: apple-container-skill3description: Use Apple's `container` CLI on Apple silicon macOS for Linux containers, OCI image builds, registries, volumes, networks, port forwarding, host access, and persistent `container machine` Linux environments. Use this skill whenever the user asks to replace Docker Desktop with Apple Container, run Linux commands on macOS, build/run/inspect Apple containers, debug Apple Container service/network/build failures, or use Container Machines.4---56# Apple Container Skill78Operate Apple's `container` CLI as a native macOS Linux container runtime. This skill should guide the agent's choices, not just provide command syntax.910## First Moves1112Before doing real work, establish whether the host and runtime are usable:1314```bash15sw_vers16uname -m17command -v container18container --version19container system status20```2122- Require Apple silicon (`arm64`). Current Apple docs support macOS 26+ for real use; macOS 15 has important networking limitations.23- Record the CLI and service versions. If the host is older than 1.2.0, recommend upgrading before diagnosing image-environment leakage, build-context failures, stalled published ports, or machine API timeouts; those are known fixed defects. Do not assume the CLI and running service are the same version.24- If the CLI is missing, prefer Apple's signed installer from the GitHub releases page. Homebrew can work, but if `container system start` fails with missing plugins after a Homebrew install, upgrade/reinstall the formula.25- Start services with `container system start` when status shows they are stopped. First start may prompt to install the recommended Linux kernel.26- Run a smoke test before blaming application code:2728```bash29container run --rm docker.io/library/alpine:latest sh -lc 'uname -a; nslookup github.com'30```3132For an unknown or flaky environment, run the bundled diagnostic:3334```bash35bash skills/apple-container-skill/scripts/diagnose.sh36```3738## Choose The Right Runtime Shape3940- Use `container run` for disposable app containers, one-shot Linux commands, project dev shells, image smoke tests, and services whose state should live in bind mounts or named volumes.41- Use `container machine` for a long-lived Linux workspace: repeated distro testing, system services, VS Code Remote SSH, a persistent root filesystem, or "edit on macOS, build inside Linux" loops.42- Do not describe machines as merely "persistent containers." A machine is a convenience wrapper around a container, a separate persistent root disk, and host integration. It maps the host user, forwards SSH agent support, and mounts the macOS home at `/Users/<user>` while the Linux user's `$HOME` is `/home/<user>`.43- Plain OCI application images may fail in machine mode because machines need a bootable init system. Plain `alpine:3.22` can create, inspect, and stop but fail command execution if `/sbin/openrc` is absent. Build an OpenRC-capable Alpine image or a systemd-capable Ubuntu/Debian image for reliable `container machine run`.44- For scripted machine commands, prefer an option terminator: `container machine run -n dev -- whoami` or `container machine run -n dev -- /bin/sh -c 'whoami; pwd; echo "$HOME"'`. Avoid `-i` in heredoc/non-interactive scripts because it can consume the rest of the script from stdin.4546## Machine Image Selection4748When the user names a distro image, preserve the distro choice but choose the runtime shape correctly:4950- For one-shot commands or app containers, use the requested image directly with `container run`.51- For `container machine`, treat the requested image as a base image unless it is already known to be machine-capable.52- Do not silently try to use plain `ubuntu`, `debian`, or `alpine` app images as long-lived machines. Explain that machines boot an init system, then derive a machine image from the requested base.53- For Alpine machines, install both `openrc` and `openrc-init`, add related user/network tools, set `CMD ["/sbin/openrc-init"]`, then build a local `*-machine` image. Installing only `openrc` and using BusyBox `/sbin/init` can start and immediately shut down.54- For Ubuntu/Debian machines, add systemd, dbus, sudo, SSH/network tools as needed, set the systemd target, and build a local `*-machine` image.55- If the user insists on the exact image without derivation, use `container run` or warn that `container machine` may create but fail to boot or execute commands.5657## Safety Rules5859Ask before:6061- Running `sudo`, installing, upgrading, uninstalling, or changing DNS resolver entries.62- Global cleanup: `container prune`, `container image prune`, `container volume prune`, `container network prune`, or deleting all resources.63- Deleting named machines, volumes, images, or containers that were not created for the current task.64- Editing `~/.ssh/config`, changing host firewall/VPN settings, or widening bind mounts beyond the project directory.65- Passing kernel arguments that disable or weaken security controls. Use `--kernel-arg` only for a documented kernel-level requirement, not ordinary application configuration.6667Prefer graceful stops (`container stop`, `container machine stop`) before forceful deletion or `container kill`.6869## Practical Defaults7071Common project shell:7273```bash74container run --rm -it -v "$PWD:/work" -w /work docker.io/library/ubuntu:24.04 bash75```7677Build and run an image:7879```bash80container build -t local/app:dev .81container run --rm -p 8080:8080 local/app:dev82```8384Long-lived machine:8586```bash87container machine create local/alpine-machine:latest --name dev --set-default --cpus 4 --memory 8G88# Run the first command from a real terminal so initial user setup has a host PTY.89container machine run -n dev -- /bin/sh -c 'whoami; pwd; echo "$HOME"'90container machine stop dev91```9293## Important Current Behaviors9495- `container system property get`, `set`, and `clear` were removed in 1.0. Use `~/.config/container/config.toml` for defaults and `container system property list` only to inspect effective config.96- Apple's signed installer places the CLI at `/usr/local/bin/container`; check that path directly when a fresh shell cannot find `container`.97- `container build` may leave the BuildKit builder running. If a validation task must leave no runtime processes, inspect `container builder status`, then use `container builder stop` and `container builder delete`.98- Host-to-container traffic should usually use `-p/--publish`; if it fails, check that the app listens on `0.0.0.0` inside the container.99- For Unix sockets, choose by direction: bind-mount (`-v`) a socket that already exists on the host into the container; use `--publish-socket host_path:container_path` when the process in the container creates the socket and the host must reach it. For non-root clients, verify ownership and mode on both endpoints. Apple Container 1.1 fixed non-root socket-mount access.100- Apple Container 1.1 also fixed relative local paths for `container cp`; on older versions, use absolute paths or upgrade rather than debugging a correct relative path.101- Apple Container 1.2 prevents an untrusted image's bare `ENV` entry from implicitly copying a same-named host variable. Still make inheritance explicit: use `-e NAME` only when host passthrough is intended, and prefer `KEY=value`, an env file, or a secret mechanism for reproducible runs.102- Use repeatable `--kernel-arg key=value` only for a kernel/security/debug requirement and verify the effective command line with `cat /proc/cmdline`. Do not use it for application settings or casually replace security defaults such as `lsm=landlock`.103- The 1.2.0 release does not expose `container run --stop-signal`, despite earlier release notes mentioning it. Put `STOPSIGNAL` in the image for a reusable default or use `container stop -s SIGNAL` for an operator-selected signal.104- Treat named volumes as single-attachment unless the workflow has proven otherwise; do not assume the same named volume can be attached concurrently to multiple running containers.105- Container-to-host traffic does not use Docker's magic host alias. Configure a localhost DNS domain:106107```bash108sudo container system dns create host.container.internal --localhost 203.0.113.113109```110111This can disable Private Relay, and packet-filter rules may need recreation after reboot.112113- `container network` user-defined networks require macOS 26+. On macOS 15, container-to-container networking and multiple networks are limited.114- If networking worked and then fails after VPN or endpoint security changes, suspect vmnet/VPN routing before changing application code.115- A machine's first command may need a real host terminal while user setup completes. If a headless first run fails with `Operation not supported by device`, retry the initialization from a PTY; subsequent non-interactive `machine run ... -- command` calls can be scripted. Do not add `-i` to a heredoc or unattended job.116117## References118119- Read [references/workflows.md](references/workflows.md) for common development, build, network, registry, volume, and machine playbooks.120- Read [references/troubleshooting.md](references/troubleshooting.md) when service startup, DNS, VPN, vmnet, builder, Rosetta, port publishing, or machine mode fails.121- Read [references/commands.md](references/commands.md) for concise command coverage after you know which workflow you need.