diff --git a/.claude-plugin/marketplace.json b/.claude-plugin/marketplace.json new file mode 100644 index 00000000..15ce4489 --- /dev/null +++ b/.claude-plugin/marketplace.json @@ -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": "./" + } + ] +} diff --git a/.claude-plugin/plugin.json b/.claude-plugin/plugin.json new file mode 100644 index 00000000..0ea08663 --- /dev/null +++ b/.claude-plugin/plugin.json @@ -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" + ] +} diff --git a/skills/README.md b/skills/README.md new file mode 100644 index 00000000..cd29a445 --- /dev/null +++ b/skills/README.md @@ -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 --help` is authoritative, and the skill tells the +agent to use it. diff --git a/skills/container/SKILL.md b/skills/container/SKILL.md new file mode 100644 index 00000000..07513b20 --- /dev/null +++ b/skills/container/SKILL.md @@ -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 --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 --help` — authoritative flags, always current diff --git a/skills/container/references/container-machines.md b/skills/container/references/container-machines.md new file mode 100644 index 00000000..a78401c8 --- /dev/null +++ b/skills/container/references/container-machines.md @@ -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 +``` diff --git a/skills/container/references/docker-migration.md b/skills/container/references/docker-migration.md new file mode 100644 index 00000000..cc5882e6 --- /dev/null +++ b/skills/container/references/docker-migration.md @@ -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 --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 && container start ` | +| `docker attach` | — | use `container exec -it sh` | +| `docker top` | — | `container exec ps aux` | +| `docker port` | — | `container inspect ` | +| `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 `, 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 `.` (`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 :`. +- `build:` → a `container build -t .` 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 ` — 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.