mirror of
https://github.com/apple/container.git
synced 2026-08-27 19:06:33 +00:00
Add container skill (#2154)
Signed-off-by: michael_crosby <michael_crosby@apple.com>
This commit is contained in:
@@ -0,0 +1,15 @@
|
||||
{
|
||||
"name": "apple-container",
|
||||
"description": "Skills for using container, Apple's tool for running Linux containers on macOS",
|
||||
"owner": {
|
||||
"name": "The container project maintainers"
|
||||
},
|
||||
"plugins": [
|
||||
{
|
||||
"name": "container",
|
||||
"description": "Teaches Claude container's command surface and how it maps to Docker, Lima, Colima, and Podman on macOS",
|
||||
"version": "0.1.0",
|
||||
"source": "./"
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,22 @@
|
||||
{
|
||||
"name": "container",
|
||||
"description": "Teaches Claude container's command surface and how it maps to Docker, Lima, Colima, and Podman on macOS",
|
||||
"version": "0.1.0",
|
||||
"homepage": "https://github.com/apple/container",
|
||||
"repository": "https://github.com/apple/container",
|
||||
"license": "Apache-2.0",
|
||||
"keywords": [
|
||||
"container",
|
||||
"containers",
|
||||
"docker",
|
||||
"lima",
|
||||
"colima",
|
||||
"podman",
|
||||
"oci",
|
||||
"macos",
|
||||
"apple-silicon"
|
||||
],
|
||||
"skills": [
|
||||
"./skills/container"
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,39 @@
|
||||
# Skills
|
||||
|
||||
Agent skills for working with `container`. A skill teaches a coding agent this tool's command
|
||||
surface — how it maps to `docker`, `lima`, `colima`, and `podman`, and where it differs.
|
||||
|
||||
## container
|
||||
|
||||
Covers the full command surface, the Docker command mapping, and the differences that
|
||||
commonly trip people up (singular command groups, `-t` on builds, the three-step DNS setup,
|
||||
container machines).
|
||||
|
||||
## Install
|
||||
|
||||
Claude Code, from a local clone:
|
||||
|
||||
```bash
|
||||
# in Claude Code, from anywhere
|
||||
/plugin marketplace add /path/to/container
|
||||
/plugin install container@apple-container
|
||||
```
|
||||
|
||||
Or straight from GitHub, without a clone:
|
||||
|
||||
```bash
|
||||
/plugin marketplace add apple/container
|
||||
/plugin install container@apple-container
|
||||
```
|
||||
|
||||
The skill loads on demand — it activates when a task involves containers, Dockerfiles, or
|
||||
images on macOS, and stays out of the way otherwise.
|
||||
|
||||
## Editing
|
||||
|
||||
`skills/container/SKILL.md` is the always-loaded surface, so keep it short and put detail in
|
||||
`skills/container/references/`.
|
||||
|
||||
Document command *names* and behavior that surprises people. Do not paste exhaustive flag
|
||||
lists — they drift. `container <command> --help` is authoritative, and the skill tells the
|
||||
agent to use it.
|
||||
@@ -0,0 +1,92 @@
|
||||
---
|
||||
name: container
|
||||
description: Use when running, building, or managing Linux containers on macOS, or when a task involves Docker, docker compose, Lima, Colima, or Podman commands on a Mac, Dockerfiles, OCI images, image registries, or setting up a Linux development environment on Apple silicon.
|
||||
---
|
||||
|
||||
# container
|
||||
|
||||
`container` runs Linux containers on macOS (Apple silicon, macOS 26+), replacing Docker, Lima,
|
||||
Colima, and Podman. It uses standard OCI images and ordinary Dockerfiles, so it pulls from and
|
||||
pushes to any registry. You do not stand up or size a Linux host first: `container system
|
||||
start`, then `container run`.
|
||||
|
||||
## Command groups are singular
|
||||
|
||||
`image`, `volume`, `network`, `registry`, `machine`, `system`. Docker's plurals do not exist —
|
||||
`container images` is not a command, `container image ls` is.
|
||||
|
||||
**Never infer a subcommand from Docker. Run `container <group> --help` and use what it lists.**
|
||||
Wrong commands here are plausible inventions, not typos.
|
||||
|
||||
An unrecognized subcommand falls through to the plugin loader, so it reports a *service*
|
||||
problem rather than a naming one — `container images` prints `Error: Plugins are unavailable.
|
||||
Start the container system services and retry`. Check the name before restarting anything; only
|
||||
trust that message when `container system status` also reports the service down.
|
||||
|
||||
## Docker → container
|
||||
|
||||
| Docker | container |
|
||||
|---|---|
|
||||
| `docker ps` / `ps -a` | `container ls` / `ls -a` |
|
||||
| `docker images` | `container image ls` |
|
||||
| `docker pull` / `push` | `container image pull` / `push` |
|
||||
| `docker rmi` | `container image rm` |
|
||||
| `docker tag` / `save` / `load` | `container image tag` / `save` / `load` |
|
||||
| `docker rm` | `container delete` (alias `rm`) |
|
||||
| `docker login` / `logout` | `container registry login` / `logout` |
|
||||
| `docker info` | `container system status` |
|
||||
| `docker compose` | no equivalent — see `references/docker-migration.md` |
|
||||
|
||||
`run`, `build`, `exec`, `logs`, `cp`, `inspect`, `stats`, `start`, `stop`, `kill`, `export`,
|
||||
`prune`, `volume *`, and `network *` match Docker, as do the common `run` flags: `-d`, `--rm`,
|
||||
`-i`, `-t`, `-e`, `-p`, `-v`, `-w`, `--name`, `--network`, `--entrypoint`.
|
||||
|
||||
`restart`, `commit`, `attach`, `top`, `rename`, `pause`, `port`, and `--restart` have no
|
||||
equivalent. Check `references/docker-migration.md` before assuming anything else exists.
|
||||
|
||||
## Gotchas
|
||||
|
||||
- **`container build` needs `-t`** — the default tag is a freshly generated UUID.
|
||||
- **`container ls` hides stopped containers.** Use `-a`.
|
||||
- **Every container gets its own IP**, reachable from the host and from other containers. `-p`
|
||||
binds a host port; it is not needed for basic reachability. Get IPs from `container inspect`.
|
||||
- **Builds run in a builder container.** If a build fails oddly, check `container builder
|
||||
status`; `container builder start` takes `--cpus` and `--memory`.
|
||||
- **Name resolution takes three steps, and works only on the `default` network:**
|
||||
|
||||
```bash
|
||||
# 1. create ~/.config/container/config.toml containing:
|
||||
# [dns]
|
||||
# domain = "test"
|
||||
container system stop && container system start # 2. reload the config
|
||||
sudo container system dns create test # 3. point macOS at container's resolver
|
||||
```
|
||||
|
||||
Then use `db.test` — a bare `db` never resolves. That file does not exist until you create
|
||||
it, and it is TOML, so edit the `[dns]` table in place rather than appending a second one;
|
||||
verify with `container system property ls`. There is no `container system dns default`
|
||||
subcommand.
|
||||
|
||||
Containers on a network from `container network create` cannot resolve each other by name at
|
||||
all ([apple/container#1809](https://github.com/apple/container/issues/1809)) — reach them by
|
||||
IP. Custom networks are for isolation, not service discovery.
|
||||
|
||||
## Container machines
|
||||
|
||||
`container run` runs an application. A **container machine** is a Linux *environment* you work
|
||||
inside: it boots the image's init system, and your username and home directory are mapped in,
|
||||
so your repos and dotfiles are on both platforms at once.
|
||||
|
||||
```bash
|
||||
container machine create alpine:latest --name dev
|
||||
container machine run -n dev # interactive shell as your host user
|
||||
```
|
||||
|
||||
Reach for this instead of Lima or Colima when you want a Linux shell rather than a single
|
||||
containerized application. See `references/container-machines.md`.
|
||||
|
||||
## Reference
|
||||
|
||||
- `references/docker-migration.md` — full mapping, what has no equivalent, replacing compose
|
||||
- `references/container-machines.md` — container machine workflows and host integrations
|
||||
- `container <command> --help` — authoritative flags, always current
|
||||
@@ -0,0 +1,120 @@
|
||||
# Container machines
|
||||
|
||||
## What it is
|
||||
|
||||
`container run` runs an **application**. A **container machine** is a Linux **environment**
|
||||
you work inside.
|
||||
|
||||
The difference that matters: a container machine boots the image's init system, so it can run
|
||||
long-running services under a process supervisor, and it maps your macOS username and home
|
||||
directory into Linux. Your repos and dotfiles are present on both platforms at the same time.
|
||||
Edit with your macOS editor; compile and run inside Linux; point macOS-native profilers,
|
||||
browsers, and GUI debuggers at the same files. There is no copy step between building
|
||||
something and inspecting it.
|
||||
|
||||
When you want a Linux shell on your Mac rather than a single containerized application, this
|
||||
is the tool — use it instead of Lima or Colima.
|
||||
|
||||
## Getting a shell
|
||||
|
||||
```bash
|
||||
container machine create alpine:latest --name dev
|
||||
container machine run -n dev # interactive shell as your host user
|
||||
container machine run -n dev uname -a # one command, then exit
|
||||
container machine run -n dev -- cat /proc/cpuinfo # use -- when the command takes flags
|
||||
```
|
||||
|
||||
`container machine run` is the way in. It boots the container machine first if it is stopped.
|
||||
You do not SSH into a container machine.
|
||||
|
||||
Inside, `whoami` returns your host username and `pwd` is your Mac home directory, mounted in.
|
||||
|
||||
## Setting a default
|
||||
|
||||
```bash
|
||||
container machine set-default dev
|
||||
container machine run # no -n needed
|
||||
```
|
||||
|
||||
## Lifecycle
|
||||
|
||||
```bash
|
||||
container machine ls # list
|
||||
container machine inspect dev # JSON detail
|
||||
container machine logs dev # boot and console logs
|
||||
container machine stop dev
|
||||
container machine rm dev # deletes its storage too
|
||||
```
|
||||
|
||||
`container machine` has the alias `m`, so `m ls` and `m run` work.
|
||||
|
||||
## Sizing and the home mount
|
||||
|
||||
`--cpus` and `--memory` at create time; `container machine set` afterward. Memory defaults to
|
||||
half of host memory. `--home-mount` takes `rw` (default), `ro`, or `none`.
|
||||
|
||||
```bash
|
||||
container machine create ubuntu:24.04 --name dev --cpus 8 --memory 16G --set-default
|
||||
container machine set -n dev cpus=4 memory=8G
|
||||
container machine stop dev # set takes effect on the next boot
|
||||
container machine run -n dev -- nproc
|
||||
```
|
||||
|
||||
`container machine set` changes configuration on disk. It does **not** apply to a running
|
||||
container machine — stop it, then `run` to reboot.
|
||||
|
||||
## Services under an init system
|
||||
|
||||
On an image with `systemd`, a container machine runs it, so system services work:
|
||||
|
||||
```bash
|
||||
container machine run -n dev -- sudo systemctl start postgresql
|
||||
container machine run -n dev -- systemctl status postgresql
|
||||
```
|
||||
|
||||
This is the main reason to reach for a container machine over `container run` for a
|
||||
development stack: dependencies live where a process supervisor manages them.
|
||||
|
||||
## One container machine per target distro
|
||||
|
||||
Each gets the same `$HOME` and the same dotfiles from your Mac, so testing across
|
||||
distributions costs almost nothing:
|
||||
|
||||
```bash
|
||||
container machine create alpine:latest --name alpine
|
||||
container machine create ubuntu:24.04 --name ubuntu
|
||||
container machine create debian:bookworm --name debian
|
||||
```
|
||||
|
||||
## Custom images
|
||||
|
||||
Any Linux image with `/sbin/init` works. Build one like any other image:
|
||||
|
||||
```bash
|
||||
container build -t local/ubuntu-machine:latest .
|
||||
container machine create local/ubuntu-machine:latest --name ubuntu
|
||||
```
|
||||
|
||||
For a `systemd` image, the Dockerfile needs `dbus` and `systemd` installed,
|
||||
`systemctl set-default multi-user.target`, and several units masked
|
||||
(`dev-hugepages.mount`, `sys-fs-fuse-connections.mount`, `systemd-update-utmp.service`,
|
||||
`systemd-tmpfiles-setup.service`, `console-getty.service`). The complete working Dockerfile is
|
||||
in the project's `docs/container-machine.md`.
|
||||
|
||||
## Nested virtualization
|
||||
|
||||
Requires Apple silicon M3 or later, macOS 15 or later, and a kernel built with `CONFIG_KVM=y`
|
||||
— the default kernel does not have it.
|
||||
|
||||
```bash
|
||||
container machine create --virtualization --kernel /path/to/vmlinux-kvm --name kvm-dev alpine:latest
|
||||
container machine run -n kvm-dev -- ls -l /dev/kvm
|
||||
```
|
||||
|
||||
Toggle on an existing container machine, then reboot it:
|
||||
|
||||
```bash
|
||||
container machine set -n dev virtualization=true kernel=/path/to/vmlinux-kvm
|
||||
container machine stop dev
|
||||
container machine set -n dev kernel= # reset to the default kernel
|
||||
```
|
||||
@@ -0,0 +1,182 @@
|
||||
# Docker → container migration
|
||||
|
||||
Complete command mapping. Verified against `container --help` and each group's `--help`.
|
||||
When a flag matters, confirm with `container <command> --help` rather than assuming Docker's
|
||||
spelling.
|
||||
|
||||
## Containers
|
||||
|
||||
| Docker | container | Notes |
|
||||
|---|---|---|
|
||||
| `docker run` | `container run` | |
|
||||
| `docker create` | `container create` | |
|
||||
| `docker start` | `container start` | |
|
||||
| `docker stop` | `container stop` | |
|
||||
| `docker kill` | `container kill` | |
|
||||
| `docker rm` | `container delete` / `rm` | |
|
||||
| `docker exec` | `container exec` | |
|
||||
| `docker logs` | `container logs` | |
|
||||
| `docker cp` | `container copy` / `cp` | `container:path` on either side |
|
||||
| `docker export` | `container export` | |
|
||||
| `docker inspect` | `container inspect` | also the way to find a container's IP |
|
||||
| `docker stats` | `container stats` | |
|
||||
| `docker ps` | `container list` / `ls` | add `-a` for stopped containers |
|
||||
| `docker container prune` | `container prune` | |
|
||||
| `docker restart` | — | `container stop <id> && container start <id>` |
|
||||
| `docker attach` | — | use `container exec -it <id> sh` |
|
||||
| `docker top` | — | `container exec <id> ps aux` |
|
||||
| `docker port` | — | `container inspect <id>` |
|
||||
| `docker commit` | — | build an image from a Dockerfile instead |
|
||||
| `docker rename`, `pause`, `unpause`, `wait`, `diff`, `update` | — | no equivalent |
|
||||
|
||||
There is no `--restart` policy flag on `container run`. Supervise long-running services with
|
||||
launchd on the host, or run them under an init system inside a container machine.
|
||||
|
||||
## Images
|
||||
|
||||
| Docker | container |
|
||||
|---|---|
|
||||
| `docker build` | `container build` |
|
||||
| `docker images` | `container image list` / `ls` |
|
||||
| `docker pull` | `container image pull` |
|
||||
| `docker push` | `container image push` |
|
||||
| `docker rmi` | `container image delete` / `rm` |
|
||||
| `docker tag` | `container image tag` |
|
||||
| `docker save` | `container image save` |
|
||||
| `docker load` | `container image load` |
|
||||
| `docker image inspect` | `container image inspect` |
|
||||
| `docker image prune` | `container image prune` |
|
||||
| `docker history` | — no equivalent |
|
||||
|
||||
`container image` has the alias `i`.
|
||||
|
||||
### Build notes
|
||||
|
||||
`container build` covers the BuildKit features people reach for most: `--platform`,
|
||||
`--target`, `--build-arg`, `--secret`, `--no-cache`, `-f`, and `-o/--output` with
|
||||
`type=oci|tar|local`.
|
||||
|
||||
Two differences worth knowing:
|
||||
|
||||
- `-t` is effectively required. The default tag is a freshly generated UUID, so a build
|
||||
without `-t` produces an image you then have to hunt for in `container image ls`.
|
||||
- Builds execute in a builder container managed by `container builder`. It starts on demand,
|
||||
but when a build fails for no clear reason, check `container builder status`. Give it more
|
||||
room with `container builder start --cpus 8 --memory 16g`.
|
||||
|
||||
## Registries, volumes, networks
|
||||
|
||||
| Docker | container |
|
||||
|---|---|
|
||||
| `docker login` / `logout` | `container registry login` / `logout` |
|
||||
| — | `container registry list` — shows current logins |
|
||||
| `docker volume create` / `ls` / `rm` / `inspect` / `prune` | `container volume create` / `list` / `delete` / `inspect` / `prune` |
|
||||
| `docker network create` / `ls` / `rm` / `inspect` / `prune` | `container network create` / `list` / `delete` / `inspect` / `prune` |
|
||||
| `docker network connect` / `disconnect` | — set `--network` when you run the container |
|
||||
|
||||
## System
|
||||
|
||||
| Docker | container |
|
||||
|---|---|
|
||||
| `docker info` | `container system status` |
|
||||
| `docker version` | `container system version` |
|
||||
| `docker system df` | `container system df` |
|
||||
| daemon logs | `container system logs` |
|
||||
| `docker events` | — no equivalent |
|
||||
|
||||
`docker system prune` has no single equivalent. Run the four prunes:
|
||||
|
||||
```bash
|
||||
container prune # stopped containers
|
||||
container image prune
|
||||
container volume prune
|
||||
container network prune
|
||||
```
|
||||
|
||||
## Replacing a compose file
|
||||
|
||||
There is no `container compose`. A compose file becomes a shell script: DNS-resolvable names
|
||||
on the `default` network, and `-d`.
|
||||
|
||||
**Do not reach for `container network create` here.** Name lookup between containers works on
|
||||
the `default` network with a domain-qualified name. It does *not* work for containers on a
|
||||
custom network — that gap is tracked as
|
||||
[apple/container#1809](https://github.com/apple/container/issues/1809). A custom network is
|
||||
for *isolating* containers; if you use one, wire the containers together by IP from
|
||||
`container inspect <name>`, not by name.
|
||||
|
||||
Set up name resolution once (all three steps — see SKILL.md):
|
||||
|
||||
```bash
|
||||
# [dns] domain = "test" in ~/.config/container/config.toml
|
||||
container system stop && container system start
|
||||
sudo container system dns create test
|
||||
```
|
||||
|
||||
Then translate the file. This compose file:
|
||||
|
||||
```yaml
|
||||
services:
|
||||
db:
|
||||
image: postgres:16
|
||||
environment:
|
||||
POSTGRES_PASSWORD: secret
|
||||
web:
|
||||
image: my-app:latest
|
||||
ports: ["8080:80"]
|
||||
environment:
|
||||
DATABASE_URL: postgres://postgres:secret@db:5432/postgres
|
||||
depends_on: [db]
|
||||
```
|
||||
|
||||
becomes:
|
||||
|
||||
```bash
|
||||
#!/bin/bash
|
||||
set -euo pipefail
|
||||
|
||||
# no --network flag: both containers land on `default`, where name lookup works
|
||||
container run -d --name db \
|
||||
-e POSTGRES_PASSWORD=secret \
|
||||
postgres:16
|
||||
|
||||
# depends_on becomes an explicit readiness check
|
||||
until container exec db pg_isready -q; do sleep 1; done
|
||||
|
||||
# db.test, not db — the name must be domain-qualified
|
||||
container run -d --name web -p 8080:80 \
|
||||
-e DATABASE_URL=postgres://postgres:secret@db.test:5432/postgres \
|
||||
my-app:latest
|
||||
```
|
||||
|
||||
Teardown:
|
||||
|
||||
```bash
|
||||
container stop web db
|
||||
container delete web db
|
||||
```
|
||||
|
||||
Mapping notes:
|
||||
|
||||
- `depends_on` has no declarative form. Compose only waits for *start*, not readiness, so an
|
||||
explicit readiness loop is usually more correct than what it replaced.
|
||||
- Reference other services as `<name>.<domain>` (`db.test`). A bare `db` does not resolve.
|
||||
This means editing your application's config, not just the `run` command.
|
||||
- `ports:` → `-p`. Often unnecessary between containers, since each container has its own IP
|
||||
and is reachable without publishing. You need `-p` to reach a service from a host browser or
|
||||
a macOS-native tool.
|
||||
- `volumes:` → `-v` for bind mounts, or `container volume create` plus `-v <name>:<path>`.
|
||||
- `build:` → a `container build -t <name> .` line before the `run`.
|
||||
- `restart:` has no equivalent. Supervise with launchd on the host, or run the stack under an
|
||||
init system inside a container machine.
|
||||
|
||||
## Features with no Docker counterpart
|
||||
|
||||
- `container machine` — a full Linux environment with your home directory mapped in. See
|
||||
`container-machines.md`.
|
||||
- `container run --publish-socket <host_path:container_path>` — publish a Unix socket to the
|
||||
host rather than a TCP port.
|
||||
- `container run --ssh` — forward your SSH agent socket into the container.
|
||||
- `container run --virtualization` — expose virtualization to the container for nested use.
|
||||
- `container run --rosetta` — run x86-64 binaries on Apple silicon.
|
||||
- `container system kernel` — manage the kernel containers boot with.
|
||||
Reference in New Issue
Block a user