Summary
This is a feature request for a Docker/Podman-compatible Unix socket backed by launchd socket activation — the macOS equivalent of how Podman works on Linux with systemd. The goal is zero idle overhead: no always-running daemon, no persistent VM. The socket exists at all times (owned by launchd), but the process that serves it is only spawned on demand when a client connects, and exits automatically after a configurable inactivity timeout.
This is the same model Podman uses on Linux: podman system service is started on-demand by systemd socket activation, handles requests, then exits. See also: https://podman-desktop.io/docs/migrating-from-docker/managing-docker-compatibility
Motivation
Today mocker is a CLI shim — it translates docker-compatible command invocations into Apple Containerization calls. That covers shell-script-based workflows, but it does not help tools that speak to unix:///var/run/docker.sock or unix:///run/podman/podman.sock directly.
Examples of affected tools:
- Podman remote client (
podman --remote)
- Docker SDK clients (Go, Python, etc.)
DOCKER_HOST-aware CI systems and GitHub Actions runners
- Tools like
lazydocker, k9s's docker provider, ctlptl, lima, etc.
How Podman solves this on Linux (the model to follow)
On Linux, Podman is truly daemonless at the execution layer — containers are child processes of conmon, not of any global daemon. The REST API socket is served by podman system service, a short-lived process activated on demand:
- systemd creates and owns the socket file (
/run/user/$UID/podman/podman.sock) — passively, with essentially zero overhead.
- When a client connects, systemd wakes up
podman system service, passing it the inherited socket file descriptor.
podman system service serves the Docker Engine / Podman REST API, translating calls into direct container operations.
- After a configurable inactivity timeout (default: 5 seconds),
podman system service exits — systemd resumes owning the socket.
No persistent daemon. No idle VM. The socket is always present, but the process behind it is ephemeral.
Proposed design: mocker system service + launchd socket activation
macOS launchd supports the identical socket-activation pattern. The proposed implementation:
1. mocker system service subcommand
A new subcommand that:
- Accepts an inherited socket file descriptor from
launchd (via the standard launchd socket activation protocol)
- Serves the Docker Engine REST API (and optionally the Podman-compatible REST API) over that socket
- Translates each API call into Apple Containerization framework calls — the same backend
mocker CLI already uses (/usr/local/bin/container, ImageStore, etc.)
- Exits after a configurable
--timeout of inactivity (e.g. --timeout 5s)
2. launchd plist (installed by mocker socket install)
A launchd user agent plist (installed to ~/Library/LaunchAgents/io.mocker.socket.plist) that:
- Declares the socket path (e.g.
~/.mocker/mocker.sock, optionally symlinked to /var/run/docker.sock)
- Configures
launchd to spawn mocker system service when a client connects
- Keeps the socket alive across reboots via
launchctl enable
Example plist structure:
<key>Sockets</key>
<dict>
<key>MockerSocket</key>
<dict>
<key>SockPathName</key>
<string>/Users/YOU/.mocker/mocker.sock</string>
</dict>
</dict>
<key>ProgramArguments</key>
<array>
<string>/usr/local/bin/mocker</string>
<string>system</string>
<string>service</string>
<string>--timeout</string>
<string>5s</string>
</array>
3. Helper commands
mocker socket install — writes the plist and runs launchctl bootstrap
mocker socket uninstall — removes the plist and runs launchctl bootout
mocker socket status — shows whether the launchd agent is loaded and the socket path
Resource footprint
| State |
What is running |
| Idle (no client connected) |
Only launchd holding the socket fd — effectively zero CPU/memory |
| Active (client connected) |
mocker system service process only — no VM, no daemon |
| Container running |
Apple Containerization manages the VM per-container, as today |
Constraints and notes
- Apple Containerization is one-VM-per-container, which differs from Docker's shared-kernel model. The API implementation should document where semantics diverge (e.g.
--pid sharing, --network host).
- Podman's REST API is a superset of the Docker Engine API in most common paths, so implementing the Docker Engine API first covers both use cases for the majority of tools.
- The existing
~/.mocker/ JSON state store serves as the source of truth, keeping socket and CLI state consistent.
mocker system service inherits the socket fd from launchd using the standard launch_activate_socket() API (available in macOS SDK, no third-party deps required).
Acceptance criteria
Current workarounds (and why they fall short)
Users who need socket-compatible tooling today can try the following, but each hits a wall quickly:
1. Shell alias or docker-symlink to mocker
alias docker=mocker
# or
ln -sf $(which mocker) /usr/local/bin/docker
Where this works: Shell scripts, docker-prefixed CLI invocations.
Where this hits a wall:
- Any tool that connects to
DOCKER_HOST or unix:///var/run/docker.sock directly fails immediately — the socket does not exist.
docker context, docker buildx, all SDKs, and podman --remote all require a live socket; an alias cannot satisfy that.
2. socat pipe fronting the mocker CLI
# This does NOT work — mocker is not a stream-protocol server.
socat UNIX-LISTEN:/tmp/mocker.sock,fork EXEC:"mocker ..."
Where this hits a wall:
- The Docker Engine API is a full HTTP/1.1 REST protocol with persistent connections, multiplexed streams (for
attach/exec/logs), and hijacked TCP for TTY. A subprocess-per-connection socat bridge cannot implement this.
- Each connection spawns a fresh stateless
mocker process — no shared state between connections.
3. Running Docker Desktop or Podman Machine alongside mocker
Where this hits a wall:
- Defeats the purpose: two runtimes with split image stores and container state.
- Both Docker Desktop and
podman machine run a persistent Linux VM — the exact idle overhead this feature aims to eliminate.
Summary
This is a feature request for a Docker/Podman-compatible Unix socket backed by launchd socket activation — the macOS equivalent of how Podman works on Linux with systemd. The goal is zero idle overhead: no always-running daemon, no persistent VM. The socket exists at all times (owned by
launchd), but the process that serves it is only spawned on demand when a client connects, and exits automatically after a configurable inactivity timeout.This is the same model Podman uses on Linux:
podman system serviceis started on-demand by systemd socket activation, handles requests, then exits. See also: https://podman-desktop.io/docs/migrating-from-docker/managing-docker-compatibilityMotivation
Today
mockeris a CLI shim — it translatesdocker-compatible command invocations into Apple Containerization calls. That covers shell-script-based workflows, but it does not help tools that speak tounix:///var/run/docker.sockorunix:///run/podman/podman.sockdirectly.Examples of affected tools:
podman --remote)DOCKER_HOST-aware CI systems and GitHub Actions runnerslazydocker,k9s's docker provider,ctlptl,lima, etc.How Podman solves this on Linux (the model to follow)
On Linux, Podman is truly daemonless at the execution layer — containers are child processes of
conmon, not of any global daemon. The REST API socket is served bypodman system service, a short-lived process activated on demand:/run/user/$UID/podman/podman.sock) — passively, with essentially zero overhead.podman system service, passing it the inherited socket file descriptor.podman system serviceserves the Docker Engine / Podman REST API, translating calls into direct container operations.podman system serviceexits — systemd resumes owning the socket.No persistent daemon. No idle VM. The socket is always present, but the process behind it is ephemeral.
Proposed design:
mocker system service+ launchd socket activationmacOS
launchdsupports the identical socket-activation pattern. The proposed implementation:1.
mocker system servicesubcommandA new subcommand that:
launchd(via the standardlaunchdsocket activation protocol)mockerCLI already uses (/usr/local/bin/container,ImageStore, etc.)--timeoutof inactivity (e.g.--timeout 5s)2. launchd plist (installed by
mocker socket install)A
launchduser agent plist (installed to~/Library/LaunchAgents/io.mocker.socket.plist) that:~/.mocker/mocker.sock, optionally symlinked to/var/run/docker.sock)launchdto spawnmocker system servicewhen a client connectslaunchctl enableExample plist structure:
3. Helper commands
mocker socket install— writes the plist and runslaunchctl bootstrapmocker socket uninstall— removes the plist and runslaunchctl bootoutmocker socket status— shows whether the launchd agent is loaded and the socket pathResource footprint
launchdholding the socket fd — effectively zero CPU/memorymocker system serviceprocess only — no VM, no daemonConstraints and notes
--pidsharing,--network host).~/.mocker/JSON state store serves as the source of truth, keeping socket and CLI state consistent.mocker system serviceinherits the socket fd fromlaunchdusing the standardlaunch_activate_socket()API (available in macOS SDK, no third-party deps required).Acceptance criteria
mocker system servicestarts, accepts alaunchd-inherited socket fd, and serves the Docker Engine REST API.mocker socket installinstalls the launchd plist and activates the socket.DOCKER_HOST=unix://~/.mocker/mocker.sock docker psworks.podman --remote --url unix://~/.mocker/mocker.sock psworks.launchd.mocker system serviceexits after the inactivity timeout andlaunchdresumes socket ownership.Current workarounds (and why they fall short)
Users who need socket-compatible tooling today can try the following, but each hits a wall quickly:
1. Shell alias or
docker-symlink tomockerWhere this works: Shell scripts,
docker-prefixed CLI invocations.Where this hits a wall:
DOCKER_HOSTorunix:///var/run/docker.sockdirectly fails immediately — the socket does not exist.docker context,docker buildx, all SDKs, andpodman --remoteall require a live socket; an alias cannot satisfy that.2.
socatpipe fronting themockerCLIWhere this hits a wall:
attach/exec/logs), and hijacked TCP for TTY. A subprocess-per-connectionsocatbridge cannot implement this.mockerprocess — no shared state between connections.3. Running Docker Desktop or Podman Machine alongside
mockerWhere this hits a wall:
podman machinerun a persistent Linux VM — the exact idle overhead this feature aims to eliminate.