Skip to content
Β 
Β 

Latest commit

Β 

History

355 Commits

Folders and files

NameName
Last commit message
Last commit date
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 

Repository files navigation

Glass Dock 🚒

Important

Both glassdock and Apple container are still under heavy development!

Note

glassdock maintains to be compatible with Docker Engine API v1.51.

Progress is tracked in #14 and #90.

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.


Quick Start ⚑

Get started with the Glass Dock CLI in a few commands:

Launch Glass Dock 🏁

./glassdock
FolderWatcher] Started watching $HOME/Library/Application Support/com.apple.container
[ NOTICE ] Server started on http+unix: $HOME/.glassdock/container.sock
...

Using Docker CLI 🐳

Glass Dock automatically registers a glassdock Docker context on startup. Activate it once:

docker context use glassdock

Then use Docker normally β€” no DOCKER_HOST needed:

docker ps        # List running containers
docker ps -a     # List all containers
docker images    # List available images

Switch 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-context

Note: 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 images

Or inline without exporting:

DOCKER_HOST=unix://$HOME/.glassdock/container.sock docker ps
DOCKER_HOST=unix://$HOME/.glassdock/container.sock docker images

Key Features ✨

  • 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.sock and auto-registers a glassdock Docker 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 glassdockctl CLI, and a Raycast extension
  • Provides one-command migration from an existing Docker engine with glassdockctl migrate from-docker

Requirements πŸ“‹

  • macOS 26 (Tahoe) on Apple Silicon (arm64)
  • The installer package, which includes the VMM, guest kernel, root disk, libkrun, and gvproxy runtime artifacts

Installation πŸ› οΈ

Homebrew

A Homebrew formula is planned. Until it is published, install from GitHub Releases or build from source.

GitHub Releases

Download Glass Dock from the current releases page. Ensure the binary has execute permissions (+x) before running it.


Usage πŸš€

Refer to Quick Start above for immediate usage examples.

Docker builds and Buildx

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.

Image names, IDs, and repeated builds

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.

Runtime and published-port recovery

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.

Volume sync mode

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 mode

Override 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"}}' # fsync

Or using Docker Compose with driver_opts:

services:
  postgres:
    image: postgres:latest
    volumes:
      - pgdata:/var/lib/postgresql/data

volumes:
  pgdata:
    driver: local
    driver_opts:
      sync: fsync

Valid modes: nosync Β· fsync Β· full

docker compose down preserves named volumes; docker compose down -v intentionally deletes them.

Engine resources

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 relay

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

Building from Source πŸ—οΈ

Prerequisites

  • 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 & Run

  1. Build the project:
make

Build 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 ./...
  1. (Optional) Format the code:
make fmt
  1. Run the debug binary:
.build/arm64-apple-macosx/debug/glassdock

The server will create the socket at $HOME/.glassdock/container.sock.

Testing βœ…

Run unit tests:

make test

Contributing 🀝

We welcome contributions!

Workflow

  1. Fork the repository and create a feature branch 🌿
  2. Open a PR against main with a clear description πŸ“
  3. Add or update tests for new behavior (see Tests/GlassDockTests) βœ”οΈ
  4. Keep changes small and focused. Document API or behavioral changes in the PR description πŸ“š

Developer Notes πŸ§‘β€πŸ’»

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 πŸ“

Piping I/O to container processes

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 tasks

Ownership rules:

  • stdout/stderr: Apple closes .write. You close .read when the reader task ends.
  • stdin: Apple closes .read. You close .write when done sending input.
  • StdioPipes.make() closes any partial pipes on EMFILE and returns nil β€” always guard let.

make test includes a lint-pipes check that fails if = Pipe() appears in application source.


Migrating from Docker

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-run

Run the migration:

glassdockctl migrate from-docker

Useful 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 report

The 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.


Runtime benchmarks

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 benchmark

The 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.


Security & Limitations ⚠️

  • 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-Auth header 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-docker works 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.

Community πŸ’¬

Join the Glass Dock community to ask questions, share ideas, or get help:

License πŸ“„

See the LICENSE file in the repository root.


Acknowledgements πŸ™

  • 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 πŸ³πŸ’»

About

Docker-compatible container engine for macOS on Apple Silicon. One persistent Linux VM, Docker CLI compatible.

Topics

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages