Important
Both glassdock and Apple container are still under heavy development!
Note
glassdock maintains to be compatible with Docker Engine API v1.51.
- Glass Dock π’
Glass Dock is a CLI/daemon that exposes a Docker-compatible REST API through one persistent Linux VM on Apple Silicon ππ¦.
It allows common Docker clients (like the Docker CLI) to interact with local containers on macOS using the Docker API surface π³π».
Glass Dock also ships native control clients: a SwiftUI menu-bar app, a
glassdockctl CLI, and a Raycast extension.
Get started with the Glass Dock CLI in a few commands:
./glassdock
FolderWatcher] Started watching $HOME/Library/Application Support/com.apple.container
[ NOTICE ] Server started on http+unix: $HOME/.glassdock/container.sock
...Glass Dock automatically registers a glassdock Docker context on startup.
Activate it once:
docker context use glassdockThen use Docker normally β no DOCKER_HOST needed:
docker ps # List running containers
docker ps -a # List all containers
docker images # List available imagesSwitch back to another runtime at any time:
docker context use colima # or "default", etc.Opt out of automatic context creation
Pass --no-docker-context to skip writing the context file on startup β useful
in CI or when managing Docker contexts manually:
glassdock --no-docker-contextNote: this flag skips creating the context but does not remove one that was
already created. To remove it: docker context rm glassdock.
Alternative: set DOCKER_HOST manually
export DOCKER_HOST=unix://$HOME/.glassdock/container.sock
docker ps
docker imagesOr inline without exporting:
DOCKER_HOST=unix://$HOME/.glassdock/container.sock docker ps
DOCKER_HOST=unix://$HOME/.glassdock/container.sock docker images- Runs one persistent Linux VM with a custom Hypervisor.framework VMM π
- Tracks Docker REST API compatibility π across 107 Moby v1.51 operations: 66 full and 41 intentional error-only routes
- Listens on a Unix domain socket
$HOME/.glassdock/container.sockand auto-registers aglassdockDocker context - Uses containerd, overlayfs, runc, and Linux namespaces for containers
- Supports create, start, stop, wait, remove, inspect, list, logs, and noninteractive exec
- Supports containerd-backed image pull, list, inspect, tag, delete, and prune operations
- Ships native control clients: a SwiftUI menu-bar app, a
glassdockctlCLI, and a Raycast extension - Provides one-command migration from an existing Docker engine with
glassdockctl migrate from-docker
- macOS 26 (Tahoe) on Apple Silicon (arm64)
- The installer package, which includes the VMM, guest kernel, root disk, libkrun, and gvproxy runtime artifacts
A Homebrew formula is planned. Until it is published, install from GitHub Releases or build from source.
Download Glass Dock from the current releases page. Ensure the binary has execute permissions (+x) before running it.
Refer to Quick Start above for immediate usage examples.
Glass Dock runs BuildKit inside the persistent engine VM and supports Buildx
through the Docker /session and /grpc relays. The classic POST /build
route supports the regular Dockerfile workflow through the persistent guest
runtime. Live coverage includes ordered ENV, WORKDIR, COPY, and RUN,
build arguments, JSON-form RUN, SHELL, multi-stage COPY --from, local
archive ADD, .dockerignore, labels, CMD, and healthchecks. Run it with
make api-compatibility-build.
The default Docker Buildx driver and an explicit docker-container builder
are supported through the Docker /grpc and /session relays. The
docker-container driver's internal /var/lib/buildkit state volume is
stored on the guest ext4 disk because BuildKit requires native Linux
filesystem operations; Docker still reports the normal volume identity.
containerd owns image content and tags. Glass Dock normalizes familiar image names before it sends them to the guest. Image list, inspect, pull, tag, delete, and prune operations use this one content store.
Glass Dock runs one persistent Linux VM through its custom VMM. The host Docker API maps container operations through one multiplexed vsock connection to a guest agent. The guest uses containerd, overlayfs, runc, and Linux namespaces for all ordinary containers. Glass Dock does not start one VM per container or use relay sidecar VMs.
Published TCP and UDP ports use one supervised gvproxy process for each VM generation. The VMM connects gvproxy to the guest virtio-net device. The guest applies DNAT from the engine ingress port to the container's private network namespace. The guest stores Docker names, labels, commands, and port mappings in containerd metadata. Glass Dock restores this state and the gvproxy forwarding rules after a daemon restart.
This alpha runtime does not import containers, images, networks, or transient state from the removed per-container-VM architecture. The first start creates a new persistent engine data disk. Keep or remove old state separately until you confirm that you no longer need it.
Named volumes default to fsync, so guest fsync() calls are flushed to the host
disk. This is the safe default for databases, write-ahead logs, and other durable
state.
nosync remains available as an explicit performance opt-in. It can be faster for
write-heavy disposable workloads, but data since the last host page-cache flush can
be lost if the Mac crashes or loses power. Do not use nosync for durable database
volumes.
Override globally β apply the same mode to all named volumes (bind mounts and anonymous volumes are not affected):
glassdock --volume-sync=fsync # default: honor guest fsyncs (durable)
glassdock --volume-sync=full # fully synchronous writes (slowest)
glassdock --volume-sync=nosync # explicit unsafe performance modeOverride per volume β docker volume create -o sync=<mode> persists the
choice for that volume regardless of the global flag:
docker volume create -o sync=fsync my-pgdata
docker run -v my-pgdata:/var/lib/postgresql/data postgres
docker volume inspect my-pgdata --format '{{index .Options "sync"}}' # fsyncOr using Docker Compose with driver_opts:
services:
postgres:
image: postgres:latest
volumes:
- pgdata:/var/lib/postgresql/data
volumes:
pgdata:
driver: local
driver_opts:
sync: fsyncValid modes: nosync Β· fsync Β· full
docker compose down preserves named volumes; docker compose down -v
intentionally deletes them.
All containers share the persistent engine VM. The defaults are 6 virtual CPUs
and a 1 GiB configured memory ceiling. Use --cpus <count> and
--memory-mib <MiB> to set the VM resources when you start Glass Dock. The VMM
reclaims guest pages through the virtio balloon device, so configured memory
and physical footprint are separate measurements. Docker per-container CPU and
memory controls are applied inside the guest's cgroup v2 hierarchy. This
includes --cpus, --cpu-shares, --cpuset-cpus, --memory,
--memory-swap, --memory-reservation, --cpuset-mems, and --pids-limit.
MemorySwap=0 follows Docker's default of twice the memory limit, while
MemorySwap=-1 means unlimited swap. Create and update fail with HTTP 400 if
the requested combination is invalid or the guest cannot delegate a required
cgroup controller.
Docker socket bind mounts are relayed to the Glass Dock Docker API socket inside the guest. This supports tools that run the following command inside a container without exposing the VMM or the guest control connection:
docker -H unix:///var/run/docker.sock ps
The relay is enabled by default for local development. Disable it with
--no-docker-socket-relay when containers must not receive Docker API access.
Run the manual integration check against the active Docker socket with:
DOCKER_HOST=unix://$HOME/.glassdock/container.sock \
scripts/verify-docker-socket-relay.sh- Swift 6.2 (requirements from Apple container)
- Xcode 26 (select the correct toolchain if installed in a custom location)
sudo xcode-select --switch /Applications/Xcode_26.0.0.app/Contents/Developer
# or
sudo xcode-select -s /Applications/Xcode-26.app/Contents/Developer- Build the project:
makeBuild the Linux/arm64 guest agent and its deterministic OCI image after a guest runtime change:
make -C Guest image
cd Guest && go test -race ./... && go vet ./...- (Optional) Format the code:
make fmt- Run the debug binary:
.build/arm64-apple-macosx/debug/glassdockThe server will create the socket at
$HOME/.glassdock/container.sock.
Run unit tests:
make testWe welcome contributions!
- Fork the repository and create a feature branch πΏ
- Open a PR against
mainwith a clear description π - Add or update tests for new behavior (see
Tests/GlassDockTests) βοΈ - Keep changes small and focused. Document API or behavioral changes in the PR description π
See Menu bar, Raycast, and control-plane development
for the native menu-bar app, the glassdockctl contract, and the Raycast
extension.
- Code organization under
Sources/GlassDock/:Routes/β Route handlers π£οΈClients/β Client integrations πUtilities/β Helper utilities π§°
- Document any public API or CLI changes in this README π
When passing I/O to ContainerClient.createProcess(stdio:) or ContainerClient.bootstrap(id:stdio:), do not use Foundation's Pipe(). Use StdioPipes from Sources/GlassDock/Utilities/DockerConnectionUtility.swift instead.
Background: on Unix, every open file/socket/pipe is identified by a small integer called a file descriptor (fd). Apple's APIs dup the fds you pass into the container and then immediately close your originals. Foundation's Pipe doesn't know this happened β when it's eventually garbage-collected, it tries to close() the same fd number again. By then, that number may have been recycled for a NIO HTTP socket, so the double-close silently kills an active connection, corrupting the event loop and causing hard-to-reproduce crashes under concurrent load (issue #107).
StdioPipes centralises allocation, EMFILE validation, and cleanup:
guard let pipes = StdioPipes.make([.stdin, .stdout, .stderr]) else { // or make(.all)
throw Abort(.internalServerError, reason: "Failed to create I/O pipes")
}
let process: ClientProcess
do {
process = try await ContainerClient().createProcess(..., stdio: pipes.stdioArray)
} catch {
pipes.closeAll() // Apple never received the fds β close all 6
throw error
}
do {
try await process.start()
} catch {
pipes.closeAfterHandoff() // Apple owns stdin.read, stdout.write, stderr.write
throw error
}
// Use pipes.stdout?.read, pipes.stderr?.read, pipes.stdin?.write in tasksOwnership rules:
- stdout/stderr: Apple closes
.write. You close.readwhen the reader task ends. - stdin: Apple closes
.read. You close.writewhen done sending input. StdioPipes.make()closes any partial pipes on EMFILE and returnsnilβ alwaysguard let.
make test includes a lint-pipes check that fails if = Pipe() appears in application source.
If you already use Docker Desktop or another local Docker engine, Glass Dock can import its images, named volumes, user-defined networks, and containers.
Start Glass Dock first, then preview the migration plan:
glassdockctl migrate from-docker --dry-runRun the migration:
glassdockctl migrate from-dockerUseful options:
glassdockctl migrate from-docker --source-host unix://$HOME/.docker/run/docker.sock
glassdockctl migrate from-docker --running-only # skip stopped containers
glassdockctl migrate from-docker --skip-volumes # skip named-volume data copy
glassdockctl migrate from-docker --json # machine-readable reportThe command reads from the source Docker socket, recreates user-defined bridge networks, transfers images by reference (falling back to image archives for local-only images), copies named-volume data, and recreates containers from their inspect data. It reports migrated, skipped, and failed items, plus warnings for configuration that cannot be carried over exactly.
Use the local benchmark harness to compare a changed Glass Dock build with other Docker-compatible engines on the same Apple Silicon Mac:
make benchmark-discover
make benchmark-preflight
make benchmarkThe benchmark guide specifies the warm-up, order, correctness, cleanup, raw-data, statistics, and external-product configuration rules, plus the comparability policy for publishing footprint and resource-sensitive results. Do not compare result values from different machines.
- Intended for local development and experimentation π
- Running third-party container workloads carries inherent risks. Review sandboxing and container configurations π
- Docker API routes are covered by the pinned Moby v1.51 compatibility matrix in
Compatibility/moby-v28.5.2-matrix.json. - Pull authentication from Docker's
X-Registry-Authheader is forwarded only to the registry named by the image reference. - Privileged containers receive OCI capabilities and a guest cgroup mount for nested tools such as BuildKit. The privilege is scoped to the Linux guest; it does not expose macOS host capabilities or devices.
- Per-container CPU, memory, swap, reservation, cpuset, and PID limits use the guest cgroup v2 hierarchy. Glass Dock rejects invalid combinations and does not silently drop a limit when a required controller is unavailable.
- Docker socket bind mounts use a guest Unix socket relay to the same Glass Dock
API socket used by the Docker CLI. A container with this mount can control
the daemon and all containers that the daemon can control. Keep the relay
enabled only for trusted workloads, or disable it with
--no-docker-socket-relay. - The VMM exports the host home directory to the trusted guest so it can serve arbitrary Docker bind requests. Containers receive only their requested bind paths. Glass Dock rejects binds that overlap its engine state, and the virtio-fs server confines all file operations beneath the exported root.
- Attach and exec streams support Docker framing, output, resize, and stdin relay. Swarm and plugin operations remain intentionally error-only because Glass Dock is a single-node runtime and does not execute Swarm schedulers or host plugin processes.
glassdockctl migrate from-dockerworks at the Docker API level. It migrates images, named local volumes, user-defined bridge networks, and container configuration. Unsupported container options are omitted and reported as warnings; Swarm-only resources and non-local volume drivers are not migrated.- Build cache accounting and pruning use the guest BuildKit cache. The compatibility matrix defines full support for the regular Dockerfile workflow and does not include Swarm or Docker plugin hosting.
- Glass Dock creates a sparse unjournaled data disk for the persistent engine, matching the earlier VZ runtime, and continues to accept existing ordered- journal disks. An unreadable or unsupported disk stops startup and remains unchanged.
Join the Glass Dock community to ask questions, share ideas, or get help:
- Discord: discord.gg/Pw9VWKcUEt β chat in real time with contributors and users
- GitHub Discussions: current repository discussions β ask questions or propose features
- GitHub Issues: current repository issues β report bugs or request features
See the LICENSE file in the repository root.
- Glass Dock is derived from Socktainer and retains its Apache License 2.0 license terms, copyright notices, and Git history.
- Built with Hypervisor.framework, libkrun, and gvproxy π
- Enables Docker CLI and other Docker clients to interact with local macOS containers π³π»