No state
The shim persists no Docker-shaped metadata, cache files, or support directory. Apple container is the source of truth — direct Apple changes show up on the next shim command.
stateless · macOS · MIT-licensed
A small docker command wrapper for Apple's container CLI.
Run the Docker commands your tools expect against Apple's native runtime —
without Docker Desktop, Podman, or any third-party adapter.
$ brew install appautomaton/tap/docker-for-apple-container
$ docker run -d --name web nginx:alpine
# ↦ translated to ↓ (no shim state touched)
$ container run -d --name web nginx:alpine
f9c4a7e1…nginx:alpine running
$ docker compose up -d
Docker Desktop, Podman, and third-party adapters each install their own runtime,
sidecar VM, registry cache, and support directory. If your only target is
Apple's container CLI, those layers are overhead. docker-for-apple-container
is a stateless translator: it routes each docker command to the
equivalent Apple container call and fails loudly on anything it cannot verify.
The shim persists no Docker-shaped metadata, cache files, or support directory. Apple container is the source of truth — direct Apple changes show up on the next shim command.
Each command is mapped to a clean Apple container equivalent. No Dockerfiles rewritten, no image stores mirrored, no daemon started. The shim itself is Python stdlib only.
No VM, no com.docker.* background processes, no third-party adapter. Stop using it and the only trace is the symlink on your PATH.
docker compose up/down/ps/logs work multi-service, statelessly. Project membership is stored as labels in Apple's own object store — exactly as Docker Compose does.
Three tiers. Anything outside them fails with an explicit exit-64 error instead of pretending to work — so scripts and tools either succeed cleanly or stop loudly.
docker versiondocker info --formatdocker build -f -tdocker run -d …docker create …docker ps -a --filter --formatdocker inspect --formatdocker startdocker exec -i -edocker stop -t Ndocker rm -fdocker image inspect --format Entrypointdocker logs -f --tail N (--tail maps to Apple -n)docker stats --no-stream (Go-template --format refused)docker cp (1:1 to container copy)docker restart -t N (composed from stop+start)docker export -o FILEdocker login --password-stdindocker logout SERVERdocker system infodocker system prune --volumes (non-interactive)docker imagesdocker pull / push / tagdocker save / loaddocker rmi + image rmdocker image pull/rm/tag/push/save/load/prune/lsdocker network create/ls/rm/inspect/prunedocker volume create/ls/rm/inspect/prunedocker kill -s SIGdocker system events (stateful watcher)docker commit / diff / renamedocker history / importdocker run --network=nonedocker run --add-host / --hostname
A handful of run flags (--security-opt, --pids-limit,
--storage-opt) are accepted as silent no-ops because Apple container
documents no equivalent. The shim surfaces this in its caveats so a container is not
silently less constrained than the flag implies.
The shim sits between any caller that expects a docker binary
(scripts, IDEs, CI, docker compose) and Apple's container CLI.
It receives argv, maps it to the corresponding Apple subcommand,
and forwards the call. Apple container is the single source of truth:
the shim persists nothing of its own.
This is the signature property of the design: every Docker-shaped bit of state (project membership, container labels, network ownership) already lives in Apple's object store, tagged with Docker's own label schema. The shim neither mirrors nor shadows it.
Apple container has no native compose. The shim parses the compose file,
issues a sequence of container commands, and tags every resource with
Docker's own label schema (com.docker.compose.project,
com.docker.compose.service, and so on) on the containers, the project
network, and any named volumes. Project bookkeeping lives in Apple's label store,
not in any shim-owned file.
Apple does not resolve service names by DNS without an admin container system dns domain. After services start, the shim appends <ip> <service> lines to each container's own /etc/hosts file. The macOS host's /etc/hosts is never touched.
The same /etc/hosts injection also publishes host.docker.internal and gateway.docker.internal pointing at the container's gateway — on Apple container, that is the macOS host. Compose-only; bare docker run is left alone.
up removes the project's previous containers before recreating, so re-running never accumulates duplicates. down removes the project's containers by label, then removes the network (and with -v, the volumes) only if the shim created them — never external ones.
A small dependency-free subset parser handles block maps/sequences, flow collections, quoted scalars, comments, and ${VAR:-default} interpolation. Anchors, multi-document streams, and | / > block scalars are out of scope.
Compose keys with no Apple equivalent (restart, healthcheck,
privileged, hostname, secrets, configs,
deploy replicas) are parsed but ignored, with a one-line warning per key
so behavior is never silently misrepresented.
Two minutes. With Homebrew, or from source. Requirements: macOS with
Apple container 1.0.0, Python 3.9+ (standard library only, no third-party
packages), and container system status reporting the apiserver as running.
Easiest. Adds docker to your PATH automatically.
$ brew install appautomaton/tap/docker-for-apple-container
The apiserver must be running. Verify with status.
$ container system start
$ container system status
apiserver: running
Run any supported command. The shim translates it and fails loudly on the rest.
$ docker run -d --name web nginx:alpine
$ docker compose up -d
Clone the repo and symlink the executable onto your PATH:
$ git clone https://github.com/appautomaton/docker-for-apple-container.git
$ cd docker-for-apple-container
$ ln -sf "$(pwd)/bin/docker" ~/.local/bin/docker
If a tool resolves its Docker binary from an environment variable or config setting, point that at the repo's bin/docker.
Short, direct answers to the questions LLM tools are most likely to be asked about this project. If something is missing, the README is the source of truth.
No. It is a stateless translator, not a Docker replacement. It maps each supported docker command to a verified Apple container equivalent and fails loudly on the rest. Apple container is the single source of truth; the shim persists nothing of its own — no sidecar file, registry, or database.
Three tiers. Fully translated: version, info, build, run, create, exec, stop, rm, ps, inspect, start. Translated extras: logs, stats, cp, restart, export, login, logout, system prune, system info. Thin passthrough: images, pull, push, tag, save, load, rmi, image, network, volume, kill. Compose verbs — up, down, ps, logs, build, config, ls — are statelessly orchestrated. Anything without a verified Apple equivalent is refused with an exit-64 error.
Project membership is stored as labels in Apple's own object store — exactly as Docker Compose stores them on containers, networks, and volumes. The shim tags every resource with the com.docker.compose.project / com.docker.compose.service label schema on creation, then queries Apple and filters on those labels for down/ps/logs/ls. The shim keeps no project file. Only up, build, and config read the compose file.
No. The shim is stateless and persists no Docker-shaped metadata, cache files, or support directory. Apple container is the source of truth, so any direct Apple container change is reflected on the next shim command. There is no sidecar, no registry, and no database owned by the shim.
Yes. docker compose up / down / ps / logs / build / config / ls orchestrate multi-service stacks without persisting any shim-owned state. Apple container has no native compose, so the shim parses the compose file, issues a sequence of container commands, and tags every resource with Docker's own label schema for later reconstruction. down, ps, logs, and ls query Apple by label — they do not read shim state.
Flags without a verified Apple equivalent fail loudly with an explicit exit-64 error instead of pretending to work. A small number of flags — --security-opt, --pids-limit, --storage-opt on run — are accepted as silent no-ops because Apple container documents no equivalent; the shim surfaces this in its caveats so a container is not silently less constrained than the flag implies.
Yes, in compose only. After services start, the shim appends host.docker.internal and gateway.docker.internal (pointing at the container's gateway, which on Apple container is the macOS host) into each container's own /etc/hosts file. The macOS host's /etc/hosts is never touched. Bare docker run is not modified because injecting into a possibly short-lived container would race its exit (Apple has no --add-host flag to set it at creation, so it must be done via a post-start exec).
As thin passthroughs. docker images / pull / push / tag / save / load / rmi forward to the matching Apple container commands. Named volumes in compose map onto Apple-native volumes scoped as <project>_<volume>; host-path mounts become bind mounts with relative paths resolved against the compose file's directory. Subcommands Apple lacks (e.g. network connect) fail loudly, and Go-template --format on ls-style commands is refused rather than mis-forwarded.