- Swift 99.7%
- Shell 0.3%
| .github | ||
| App | ||
| assets | ||
| Gantry.xcodeproj | ||
| Packages | ||
| scripts | ||
| Tools | ||
| .gitignore | ||
| appcast.xml | ||
| CHANGELOG.md | ||
| CODE_OF_CONDUCT.md | ||
| CONTRIBUTING.md | ||
| Info.plist | ||
| LICENSE | ||
| README.md | ||
| SECURITY.md | ||
Gantry
Native Docker management for your Mac. Local and over SSH. Agent-ready with a built-in MCP server. Free, open source, no limits.
Gantry is a fully native macOS app (SwiftUI, Swift 6) for managing and monitoring Docker — the local daemon and any number of remote hosts over SSH — plus apple/container hosts on your Mac. No Electron, no subscription, no artificial limits. It is built to be driven by AI agents too: a bundled MCP server and App Intents expose your Docker hosts to Claude, Shortcuts, Siri and scripts.
Website: https://getgantry.github.io/
Features
Core
- Containers — list, inspect, start/stop/restart/kill/pause, rename, remove,
create and run with ports/env/volumes/restart-policy, commit to image,
export filesystem, view processes (
top), update restart policy - Images — list, build from a Dockerfile (drag-and-drop, live build log), history, tag, pull with per-layer progress, remove, prune
- Private registries — pulls resolve credentials from your
~/.docker/config.json(inlineauth, credential helpers, macOS Keychain), the samedocker loginyou already ran — so create-and-run, Quick Run and Compose pull private images, not just the manual Pull sheet - Volumes / Networks — list, inspect, create, remove, prune, connect/disconnect containers
- Docker Compose — containers grouped by compose project with collapsible sections and group start/stop/restart; run a compose file on any host (local Docker, SSH or apple/container — the runner adapts per engine) with an inline YAML editor, and open merged stack logs for a whole project
- System — disk usage (
docker system df), prune build cache - Smart Cleanup — a Reclaim Space panel that shows dangling images, stopped containers, unused volumes and build cache with their sizes and prunes each, or all at once, reporting how much was freed
Live
- Logs — streamed in real time with follow mode, regex or literal Cmd+F search with match highlighting and next/previous navigation, a minimum-level filter (Error/Warn/Info/Debug/Trace), ANSI color rendering, and a 50k-line virtualized buffer
- Stats — CPU, memory, network and disk I/O charts (Swift Charts), 1s sampling
- Events-driven UI — lists update live from the Docker events stream, with polling fallback
Notifications
- Failure alerts — a native macOS notification the moment a container crashes (non-zero exit), runs out of memory, or fails its health check, on any host (local, SSH, apple/container). Derived from the live events stream
- Click through — opening a notification activates Gantry and jumps straight to the offending container's detail view
- No false alarms — stops, restarts, kills and removes you trigger from Gantry are never reported as crashes, and an unhealthy container alerts once per episode, not on every health re-check. Toggle the whole thing in Settings ▸ General
Fleet & hosts
- Fleet dashboard — every connected host on one screen: live CPU/memory sparklines (10-minute rolling window), container state breakdown, host facts; opens at launch
- Health column — failed connections and unhealthy / restarting / dead containers across all hosts, each row jumping straight to the culprit
- Host overview — CPU/memory gauges, Docker disk usage, daemon facts
- Host terminal & files (SSH hosts) — a shell on the host itself and an SFTP file browser, alongside the per-container ones
- Auto-reconnect — dropped tunnels and transient connect failures retry with backoff; stale data stays on screen instead of blanking out
Terminal & Files
- Exec terminal — full terminal emulation (SwiftTerm) into any running container, local or remote
- File browser — browse the container filesystem, download and upload files, drag & drop between Finder and the container (tar-packed transparently)
- Drop any folder into a container — upload whole folder trees from the
picker or by dropping them from Finder. Works on every engine, including
apple/container (where there is no archive endpoint, it streams a tar through
tar -xover exec)
Ports & networking
- Open in browser — one click on a published port opens it in your browser: directly for local Docker and apple/container, through an automatic forward for SSH hosts
- Local port forwarding (SSH) — forward a remote container port to
localhost(anssh -L-style tunnel over a dedicated connection). If the local port is taken, Gantry picks a free one — and you can edit it. Active forwards are listed with open/copy-URL/stop controls
Remote hosts over SSH
- Connects exactly like
docker -H ssh://user@host: an SSH exec channel runsdocker system dial-stdioand Gantry speaks HTTP/1.1 to the remote daemon over it — nothing to install on the server beyond Docker itself - Reads
~/.ssh/config(aliases, HostName, User, Port, IdentityFile, ProxyJump — bastion chains with per-hop auth and host key checks); the Add Host sheet offers one-click import of your config hosts and ofssh://Docker contexts (docker context ls) - ed25519 and RSA keys (openssh-key-v1, optional passphrase), password auth; RSA signs with rsa-sha2-256 so it works against modern OpenSSH servers
- Host key verification with trust-on-first-use prompts (SHA256 fingerprints),
honoring
~/.ssh/known_hosts; secrets live in the macOS Keychain
apple/container hosts
- Add an Apple Container host to manage Linux containers run by apple/container side by side with your Docker hosts
- Zero-setup onboarding — on first launch (and after Gantry updates) it
checks for the
containerCLI and offers to install or upgrade a supported version through Homebrew for you, so the runtime is ready before you add a host - Driven through the
containerCLI and translated to the same engine interface, so lifecycle, live logs, exec terminal, stats, create-container, images (pull/tag/delete/prune), volumes, networks and disk usage all work from the same UI — and through the MCP server and App Intents - Connecting the host starts the
containerservices if they are down - Service control — start/stop the
containerbackground services from the menu bar and Settings ▸ Apple, with live running/stopped status - Open by IP & hostname — every apple/container gets a routable IP on your
Mac; the container's Overview surfaces that address (and, when it was launched
on a local domain, its
name.domainhostname) with one-click open-in-browser per port - Local DNS domains — create/list/delete apple/container DNS domains from
Settings ▸ Apple (admin-approved), and star a default: Gantry writes it
into apple/container's
config.tomland restarts the services for you, so the whole flow works without touching the CLI - Automatic DNS names (OrbStack-style) — new containers are assigned the
default domain automatically with a unique, image-derived name, so each one
resolves as
name.domainacross your Mac; the resolver cache is flushed so it works immediately. A DNS domain can also be chosen in New Container and Quick Run - Assign / Change DNS Name — set or change a container's domain after creation from its Address section. apple/container fixes the domain at create time, so Gantry recreates the container, preserving its image, command, environment, published ports, volume binds, restart policy and labels (named volumes are kept)
- Quick Run — an OrbStack-style quick-launch sheet: pick a local image, name it, share a macOS folder, map ports and pick a DNS domain, then run — and the container's address opens straight in the browser
- Run docker-compose files from Finder — right-click a
docker-compose.ymland choose Open With ▸ Gantry or Quick Actions ▸ Compose Up in apple/container (or File ▸ Open Compose File…). Gantry parses the file, builds anybuild:services, creates the project's network and named volumes, and starts every service independs_onorder with the standardcom.docker.compose.*labels — so the project groups in the sidebar just like Compose on Docker. apple/container has no nativecompose; Gantry does the orchestration itself over thecontainerCLI - Features the platform does not offer (pause, rename, commit, restart policies, network attach, image history, file download) are hidden for these hosts instead of erroring
Mac-native
- Three-column split view, Liquid Glass materials, dark/light/system appearance
- Collapsible host sections in the sidebar; reorder hosts via Move Up/Down
- Menu bar panel listing running containers per host — open one in your browser,
copy its
dns/ip:port, stop/restart inline, or click through to its details - Keyboard shortcuts (Cmd+R refresh, Cmd+N new container, Cmd+F log search)
- Terminal themes (System, Solarized Dark, Dracula, Nord) for the embedded shell
- Optional Touch ID confirmation before removing a container or deleting a host
- Auto-updates via Sparkle (EdDSA-signed appcast)
Agent-friendly
-
App Intents — list/start/stop/restart containers and fetch logs from Shortcuts, Siri, Spotlight, or scripts (
shortcuts run); works even when the app is closed -
MCP server — a bundled
gantry-mcpbinary exposes hosts, containers, images, volumes, networks, logs, stats, exec and disk usage as Model Context Protocol tools over stdio, so AI agents can manage your Docker hosts (including SSH ones):claude mcp add gantry -- /Applications/Gantry.app/Contents/Resources/gantry-mcpHeadless SSH connections only use hosts whose keys you already trusted in the app.
-
Copy as Prompt — every container (list, detail view with ⌥⌘P, menu bar, dashboard issues) can be copied as a paste-ready debugging prompt for an AI coding agent. The prompt carries the host and how to reach it (the exact
sshcommand or the local socket), the Gantry MCPhost_idand tool names, the container's identity and state — image, health, exit code, restarts, ports, compose project — and a task matched to the symptom: unhealthy asks to debug the failing health check, crash-looping to find the crash, healthy leaves room for your own question. Paste into Claude Code and let it dig in.
Install
Homebrew
brew install --cask getgantry/tap/gantry
xattr -dr com.apple.quarantine /Applications/Gantry.app
The xattr step clears the quarantine flag — the app is not notarized and
macOS refuses to open it otherwise.
Updating: because the app is ad-hoc signed (not notarized), in-app Sparkle auto-updates can't launch their installer under Gatekeeper, so updates go through Homebrew:
brew upgrade --cask gantry # or: brew reinstall --cask gantry
xattr -dr com.apple.quarantine /Applications/Gantry.app
Manual
-
Download the latest zip from Releases
-
Unzip and drag Gantry.app to Applications
-
First launch: right-click the app and choose Open (the app is not notarized), or clear quarantine:
xattr -dr com.apple.quarantine /Applications/Gantry.app
Requires macOS 26 (Tahoe) or later. Works with Docker Desktop, OrbStack,
Colima, or a plain remote dockerd — the socket is auto-discovered
($DOCKER_HOST, ~/.docker/run, ~/.orbstack/run, ~/.colima, /var/run).
Updates arrive automatically via Sparkle once installed.
Building from source
git clone https://github.com/getgantry/gantry.git
cd gantry
open Gantry.xcodeproj # Xcode 26+, build the "Gantry" scheme
No setup steps. The project is a thin hand-written .xcodeproj whose code
lives in local Swift packages.
Repository layout
App/ SwiftUI app target (views, intents, app shell)
Packages/
DockerKit/ Docker Engine API client
Transport/ unix socket (async-http-client), SSH dial-stdio glue,
raw-NIO hijack for exec, hand-rolled HTTP/1.1 codec
Endpoints/ containers, images, volumes, networks, exec, archive,
system, streaming (logs/stats/events)
Streams/ multiplexed log demuxer, JSON-lines decoder,
stats model with the docker CLI CPU/memory formulas,
tar reader/writer
SSHKit/ SSH layer on Citadel: key loading, ssh_config parser,
known_hosts + TOFU, SSHDialStdioTransport, local port
forwarding (SSHPortForward)
AppCore/ @Observable stores, hosts persistence, Keychain,
headless connections for Intents/MCP
GantryMCP/ stdio MCP server executable (bundled into the app)
scripts/release.sh release build + Sparkle appcast
Tools/ app icon generator
How the SSH transport works
docker -H ssh:// does not forward the remote unix socket. It runs
docker system dial-stdio on the server, which bridges the SSH channel's
stdin/stdout to the daemon socket. Gantry does the same with
Citadel: one persistent exec channel
carries serialized HTTP/1.1 requests (FIFO, keep-alive), and every streaming
endpoint (logs, stats, events, exec) gets a dedicated channel so nothing blocks.
A 101 Upgraded response switches the parser into raw passthrough — that is
how the interactive terminal rides the same machinery.
About the forks
Gantry currently builds against two small forks, both intended for upstreaming:
- andrewkomkov/swift-nio-ssh
(
gantry-fixes) — fixes a process-killingpreconditionFailurewhen a pending read delivers a window adjust on a locally closed channel, and lets a key type declare a distinct userauth algorithm name - andrewkomkov/Citadel (
gantry) — RSA signing with rsa-sha2-256 (RFC 8332) instead of legacy ssh-rsa, which modern OpenSSH servers reject
Tests
swift test --package-path Packages/DockerKit # parsers, codecs, tar, exec
swift test --package-path Packages/SSHKit # ssh_config, known_hosts, keys
Live integration tests are gated: they run automatically only when a local Docker socket exists (lists, streaming, exec, pull, prune, archive) or when the SSH test host is reachable (dial-stdio, exec, stream-cancellation stress, RSA auth). On CI they skip cleanly.
Releasing
scripts/release.sh 0.6.0
gh release create v0.6.0 dist/Gantry-0.6.0.zip --title "Gantry 0.6.0"
git add appcast.xml CHANGELOG.md && git commit -m "Release 0.6.0" && git push
The script builds the app, embeds gantry-mcp, zips, signs the Sparkle appcast
entry (EdDSA key in the login Keychain), and stamps the ## [Unreleased]
section of CHANGELOG.md with the version and date — so the
only manual changelog work is adding bullets under Unreleased as you go.
Known limitations
- Not notarized (no Apple Developer ID) — first launch needs right-click Open
- ECDSA private keys in OpenSSH format are not supported (ed25519/RSA are)
- Remote hosts need
dockeronPATHfordial-stdio(any recent version)


