All skills
sanjay3290 avatar

/apple-container

@a69d057

Apple's open-source `container` CLI to build, run, and manage OCI/Linux containers as lightweight per-container VMs on Apple-silicon macOS — no Docker daemon required. Use when the user mentions the `container` CLI, "apple container", running or building containers on macOS without Docker/Podman, `container run`, `container build`, `container images`, `container system start`, pushing/pulling images to a registry, or container networking on macOS. This is Apple's `container` tool specifically (repo `apple/container`), NOT Docker, Podman, containerd, nerdctl, or Kubernetes — the CLI is Docker-like but is a different tool.

Use this Skill: https://skilld.dev/gh/sanjay3290/ai-skills/apple-container

This session only. Nothing lands on disk.

referencesworkflows.md

≈4.5k tokens on demand. Your agent reads this file only when SKILL.md points to it.

container — Task-Oriented Workflows

Copy-pasteable recipes for Apple's container CLI (apple/container) — Linux containers as lightweight VMs on Apple-silicon macOS. This is Apple's container, not Docker: flags, defaults, and the daemon model differ. See commands.md for the full flag reference. Use fully-qualified image refs (docker.io/library/alpine) in scripts. container targets macOS 26; several features are gated (called out inline).

Version: recipes track the 1.0.0 release. container cp, container prune, container image prune, and container system version used below were added in 1.0.0 (not in 0.7.1) — run container --help to confirm your installed surface.

Recipes


First run: install + start + first container

There is no CLI installer command — download and double-click the signed .pkg, then start the services and run a container.

# 1. Download the signed installer .pkg from the GitHub releases page:
#    https://github.com/apple/container/releases
#    Double-click the .pkg and follow the prompts (admin password installs under /usr/local).

# 2. Start the background services. On first run this prompts to install the default Linux kernel.
container system start

# 3. Verify (empty list is expected on a fresh install).
container list --all

# 4. Run your first container.
container run --rm -it docker.io/library/alpine:latest sh

Note: double-clicking the .pkg is the only install path; container system start prompts for the recommended kernel (or use container system kernel set --recommended).


Run interactive vs detached

# Interactive shell (-i keeps stdin open, -t allocates a TTY; commonly combined as -it).
container run --rm -it docker.io/library/ubuntu:latest /bin/bash

# Detached, named, auto-removed on stop.
container run -d --name web --rm docker.io/library/nginx:latest

# One-shot command in the foreground (exits when the command finishes).
container run --rm docker.io/library/alpine:latest uname -a

Note: foreground by default; stdin stays closed unless -i. --rm deletes the container after it exits.


Publish ports

-p / --publish forwards loopback traffic to the container. Spec: [host-ip:]host-port:container-port[/protocol] (tcp/udp, case-insensitive).

# host 8080 -> container 80
container run -d --rm -p 8080:80 docker.io/library/nginx:latest

# bind to a specific host IP
container run -d --rm -p 127.0.0.1:8080:8000 docker.io/library/node:latest npx http-server -a :: -p 8000

# IPv6 loopback (quote the bracketed address)
container run -d --rm -p '[::1]:8080:8000' docker.io/library/node:latest npx http-server -a :: -p 8000

# UDP
container run -d --rm -p 5353:5353/udp docker.io/library/alpine:latest

Note: if a container attaches to multiple networks, published ports forward to the IP of the first network's interface.


Set environment variables

# Individual vars (-e / --env); repeatable. Bare `KEY` inherits from the host.
container run --rm -e NODE_ENV=production -e PORT=8080 docker.io/library/node:latest env

# Inherit HOME from the host shell
container run --rm -e HOME docker.io/library/alpine:latest sh -c 'echo $HOME'

# From a file (KEY=value per line; # comments and blanks ignored); repeatable.
container run --rm --env-file ./app.env docker.io/library/alpine:latest env

Note: -e and --env-file may be combined and each repeated; later values win.


Mounts: bind, volume, tmpfs

# Bind-mount a host folder (-v host:container). Use absolute host paths.
container run --rm -v "${HOME}/Desktop/assets:/content/assets" docker.io/library/python:alpine ls -l /content/assets

# Same via --mount key=value syntax.
container run --rm --mount source="${HOME}/Desktop/assets",target=/content/assets docker.io/library/python:alpine ls /content/assets

# Named volume (create first, then mount by name).
container volume create data
container run --rm -v data:/var/lib/app docker.io/library/alpine:latest sh

# Anonymous volume (-v /path) — NOT auto-removed by --rm; delete it yourself.
container run --rm -v /data docker.io/library/alpine:latest sh
# It leaves an anon-<id> volume. List to find the exact name, then remove THAT one by name:
container volume list          # locate the new anon-<id> row
container volume rm anon-<id>  # ⚠️ delete the specific volume — do NOT `grep anon | rm`, which
                              #    would match (and destroy) every anonymous volume on the machine

# Read-only bind via --mount.
container run --rm --mount type=bind,source="${PWD}/conf",target=/etc/conf,readonly docker.io/library/alpine:latest cat /etc/conf/app.ini

# tmpfs (in-memory) mount; repeatable.
container run --rm --tmpfs /scratch docker.io/library/alpine:latest sh -c 'df -h /scratch'

Note: unlike Docker, anonymous volumes do not auto-clean with --rm — remove them via container volume rm.


Build an image from a Dockerfile

# Build from ./Dockerfile in the current context, tag as web-test.
container build -t web-test .

# Explicit Dockerfile path.
container build -f docker/Dockerfile.prod -t web-test:prod .

# Build, then run it.
container build -t web-test .
container run -d --name my-web-server --rm web-test

Note: with no -f, the builder looks for Dockerfile then Containerfile. The builder runs in its own VM (see resource-limits recipe to size it).


Multi-tag, build-args, no-cache, target

# Multiple tags on one build (-t repeatable).
container build -t my-app:latest -t my-app:v1.0.0 .

# Build-time variables (repeatable).
container build --build-arg NODE_VERSION=18 --build-arg ENV=prod -t my-app .

# Skip the build cache.
container build --no-cache -t my-app .

# Target a specific multi-stage build stage.
container build --target production -t my-app:prod .

# Multi-architecture image (arm64 + amd64 via Rosetta).
container build --arch arm64 --arch amd64 -t docker.io/me/web-test:latest .

Note: --arch is repeatable/comma-separated; --platform (os/arch[/variant]) takes precedence over --os/--arch.


Tag & push to Docker Hub / private registry

Registry commands live under the Image group (container registry, alias r). Credentials are stored in the macOS keychain.

# Log in (prompts for username + password, or use -u / --password-stdin).
container registry login docker.io
container registry login -u alice ghcr.io
echo "$TOKEN" | container registry login -u alice --password-stdin ghcr.io

# HTTP for a local registry.
container registry login --scheme http localhost:5000

# Tag the local image with a full registry reference.
container image tag web-test docker.io/alice/web-test:latest

# Push it (the final reference is printed on success).
container image push docker.io/alice/web-test:latest

# Private registry.
container image tag web-test registry.example.com/fido/web-test:latest
container image push registry.example.com/fido/web-test:latest

# List saved logins / log out.
container registry list   # 1.0.0+ (added after 0.7.1)
container registry logout ghcr.io

Note: the default registry is docker.io, so a bare web-test:latest resolves against Docker Hub. Change it via the registry.domain property (next recipe).


Set the default registry

# Inspect current properties (includes [registry] domain).
container system property list

Edit ~/.config/container/config.toml:

[registry]
domain = "registry.example.com"

Note: with registry.domain set, unqualified image names resolve against that host instead of docker.io.


Container-to-container networking

Each container gets its own IP on the default vmnet network. Read it from container ls or container inspect.

# Start a server, then find its IP.
container run -d --name my-web-server --rm web-test
container ls                       # IP column shows e.g. 192.168.64.3

# Scriptable IP lookup via inspect + jq.
container inspect my-web-server | jq -r '.[0].networks[0].address'

# Reach it from another container by IP (requires macOS 26 — see note).
container run --rm -it web-test curl http://192.168.64.3

Note: container-to-container access over the virtual network requires macOS 26 and does not work on macOS 15, where containers are isolated from one another at the network layer (see concepts.md §3) — a naming scheme cannot restore connectivity. The DNS domain in the next recipe resolves names host-side only (host → container); it does not enable container-to-container traffic on macOS 15.


Local DNS domain for name resolution

Register a local domain so <name>.<domain> resolves to a named container's IP. dns create/delete need sudo (writes under /etc/resolver).

# Create the domain (admin password required).
sudo container system dns create test

Make it the default DNS domain by setting dns.domain in ~/.config/container/config.toml (there is no property set command — properties are edited in the config file):

[dns]
domain = "test"
# Now a named container is reachable as <name>.<domain>.
container run -d --name my-web-server --rm web-test
curl http://my-web-server.test

# List / remove domains.
container system dns list
sudo container system dns delete test

# Confirm the effective domain.
container system property list        # shows [dns] domain = "test"

Note: container system property only supports list/ls — set dns.domain via config.toml, not a CLI setter. A container named my-web-server with domain test answers at my-web-server.test.


Custom isolated networks (macOS 26)

The entire container network group requires macOS 26 (absent on macOS 15, which has only the single default subnet).

# Create an isolated network.
container network create foo

# With custom subnets.
container network create foo --subnet 192.168.100.0/24 --subnet-v6 fd00:1234::/64

# Attach a container to it.
container run -d --name my-web-server --network foo --rm web-test

# List / delete (detach all containers first).
container network list
container stop my-web-server
container network delete foo

Note: networks are mutually isolated — a container on foo has no connectivity to containers on default.


Inspect a container

# Full pretty JSON (one or more IDs).
container inspect my-web-server

# Extract specific fields with jq.
container inspect my-web-server | jq -r '.[0].status'
container inspect my-web-server | jq -r '.[0].networks[0].address'

# Table/JSON listing of all containers, including stopped.
container ls --all
container ls --all --format json | jq '.[] | [.configuration.id, .networks[0].address]'

Note: inspect requires at least one ID; a missing ID errors. container image inspect <ref> does the same for images.


View logs

# Application stdio.
container logs my-web-server

# Follow live output.
container logs -f my-web-server

# Last N lines (-n is short-only; there is no --lines).
container logs -n 100 my-web-server

# VM boot / init logs.
container logs --boot my-web-server

Note: --boot shows the VM kernel + vminitd init log, useful for boot/networking diagnostics.


Exec into a running container

# Interactive shell in a running container.
container exec -it my-web-server sh

# One-off command.
container exec my-web-server ls /content

# With env / as a specific user.
container exec -e DEBUG=1 -u root my-web-server env

Note: exec targets a running container and shares the same process options as run (-e, --env-file, -u, -w, etc.).


Copy files in and out

Exactly one side must be a container_id:path reference. The container must be running.

# Host -> container.
container cp ./config.json my-web-server:/etc/app/

# Container -> host.
container cp my-web-server:/var/log/app.log ./logs/

Note: container copy and container cp are the same command.


Resource limits (CPU / memory)

Container defaults: 4 CPUs, 1 GiB RAM. Builder VM defaults: 2 CPUs, 2 GiB RAM.

# Per-container limits on run/create.
container run --rm --cpus 8 --memory 32g docker.io/library/alpine:latest sh

# Short flags.
container run --rm -c 2 -m 1G docker.io/library/node:latest

# Size the builder VM before a heavy build.
container builder start --cpus 8 --memory 32g
container build -t big-app .

# Change a running builder: stop, delete, restart with new limits.
# ⚠️ `builder delete` discards the BuildKit builder and its layer cache — the next build
#    re-fetches base images and rebuilds every layer from scratch.
container builder stop
container builder delete
container builder start --cpus 8 --memory 32g

Note: memory accepts K/M/G/T/P suffixes. Monitor live usage with container stats (--no-stream for a single snapshot).


Cleanup: stop, remove, prune

⚠️ Scope matters. The -a/--all/prune variants act on every container, image, volume, or network on the machine — not just this project's. container volume prune and container image rm --all permanently delete data belonging to unrelated containers. To clean up after one workload, use the targeted commands (by name/tag); reach for the machine-wide ones only when you deliberately want a full sweep. Do not paste this whole block blindly.

# --- Targeted (scoped to named resources — safe for cleaning up one workload) ---
container stop my-web-server            # stop one container
container delete my-web-server          # rm is an alias; add -f to remove a running one
container image delete web-test         # remove one image by name/tag
container volume rm data                # remove one volume by name

# --- Machine-wide (destroys ALL matching resources — use deliberately) ---
container stop -a                       # stop every running container
container rm --all                      # delete every container
container image rm --all                # delete every image
container image prune                   # remove dangling (untagged) images only
container image prune -a                # remove every image not used by a container
container volume prune                  # delete every volume with no container reference
container network prune                 # macOS 26+; delete every unused network
container prune                         # remove all stopped containers

# Check disk usage across images/containers/volumes.
container system df

Note: container prune only removes stopped containers. image prune (no -a) removes only dangling images.


Upgrade / downgrade / uninstall

Both scripts install to /usr/local/bin. Stop the services first.

# Always stop before upgrading/downgrading.
container system stop

# Upgrade to the latest release.
/usr/local/bin/update-container.sh

# Downgrade: uninstall (keep user data with -k), then pin the previous release.
/usr/local/bin/uninstall-container.sh -k
/usr/local/bin/update-container.sh -v 0.7.1   # replace with the version you want

# Restart after any change.
container system start

# Uninstall. Default to -k (keep user data) unless you truly want everything gone.
/usr/local/bin/uninstall-container.sh -k      # keep images/containers/volumes for a later reinstall
/usr/local/bin/uninstall-container.sh -d      # ⚠️ also deletes ALL user data (images, containers, volumes) — irreversible

Note: you can also upgrade/downgrade by re-downloading and double-clicking the signed .pkg. -v pins update-container.sh to a specific version.


Troubleshooting

# "XPC connection error" / commands hang -> services aren't running. Start them.
container system start

# Confirm services are up.
container system status

# View service logs (default last 5m; --last <n>[m|h|d], -f to follow).
container system logs
container system logs --last 1h
container system logs -f

# Add --debug to any command for verbose output (or set CONTAINER_DEBUG).
container --debug run --rm docker.io/library/alpine:latest true

# Version info for CLI + API server.
container system version

Note: an XPC/connection error means the background API server isn't started — run container system start. Feature gates to keep in mind: container-to-container access and the container network group require macOS 26 (both unavailable on macOS 15). The CLI refuses to run under Rosetta — Apple silicon only.

Source: SKILL.md on GitHub

1 alert2mo3 checks · Risk SAFE
  • Gen Agent Trust Hub2mo

    The skill provides comprehensive documentation and workflows for Apple's `container` CLI, an open-source tool for managing OCI containers on macOS using lightweight virtual machines. The analysis found no malicious patterns. The tool uses standard security practices like VM-level isolation for containers and the macOS Keychain for registry credentials. Commands requiring elevated privileges, such as configuring local DNS domains, are clearly marked and intended for system configuration. External resources are sourced from trusted organizations like Apple and established open-source projects like Kata Containers.

  • Socket2mo

    No alerts

  • Snyk2mo

    Risk: CRITICAL · 2 issues

Signed by skilld at a69d057. This ties the file your Agent reads to that commit on GitHub. It does not review the instructions.

Last checked against GitHub 3 weeks ago.

Activeupdated 3 months ago
metadata
{
  "author": "sanjay3290",
  "version": "1.0"
}

README badge

README badge for sanjay3290/ai-skills/apple-container