Skip to content

open-southeners/ContainerApp

Repository files navigation

ContainerApp

A native macOS menu-bar app and dashboard for Apple's container CLI. Inspect, control, and shell into containers without leaving the menu bar.

macOS 26+ Swift 6 License

The app shells out to the container binary — there is no daemon or framework binding. It expects the binary at /usr/local/bin/container (the official package location), /opt/homebrew/bin/container, or a custom path configured in Settings.

Installation

Add the Open Southeners Homebrew tap, then install ContainerApp:

brew tap open-southeners/tap
brew install --cask open-southeners/tap/container-app

Features

Menu-bar popover

  • System status indicator (running / stopped / not installed)
  • Up to five running containers with quick Logs, Shell, and Stop actions
  • Start / Stop System controls
  • Show All Containers opens the full dashboard
  • Refreshes automatically on popover open

Dashboard window

  • Filterable container list (All / Running / Stopped) with CPU and memory columns
  • Live stats polling every 5 seconds while the window is visible
  • Per-container detail panel with four tabs:
    • Overview — image, state, ports, network
    • Logs — last 100 lines with Refresh and Copy
    • Inspect — raw JSON output, monospaced
    • Stats — CPU % and memory
  • Actions: Stop, Kill, Delete, Prune, Open Shell

Compose projects

  • Register docker-compose files via a file picker; projects persist across launches
  • Per-project and per-service Up, Down (stops containers, does not remove them), and Build actions
  • Live per-service status derived from container list by matching the <project>-<service> naming convention
  • Last command output panel for each project
  • "Show Container" shortcut links compose services into the existing detail panel (logs, stats, inspect)
  • Full-area install prompt with brew install container-compose instruction when the binary is missing

Settings

  • Custom CLI path override (persisted via @AppStorage)
  • Custom container-compose CLI path override

Building

The Xcode project is generated by XcodeGen and is gitignored. Regenerate it before opening in Xcode or after changing project.yml.

# Install XcodeGen if needed
brew install xcodegen

# Generate the project and build
xcodegen generate
xcodebuild -scheme ContainerApp -destination 'platform=macOS,arch=arm64' build

Running tests

xcodebuild -scheme ContainerApp -destination 'platform=macOS,arch=arm64' test

The test suite includes:

  • Unit tests — decoder tests against real CLI fixture output (Fixtures/), stats merge logic, and TerminalLauncher shell-command generation. Always run.
  • Integration tests (ContainerCLIRuntimeIntegrationTests) — skipped automatically when the CLI is absent or the system is stopped; creates and cleans up scratch containers when it runs.

Run a single suite:

xcodebuild -scheme ContainerApp -destination 'platform=macOS,arch=arm64' \
  test -only-testing:ContainerAppTests/ContainerCLIModelsTests

Distribution

App Sandbox is disabled — the app must spawn the CLI and communicate with its XPC apiserver. Distribution is via Developer ID, not the Mac App Store. Opening a shell in a container triggers a one-time macOS Automation consent prompt (declared via NSAppleEventsUsageDescription).

Architecture

View → ContainersViewModel → ContainerRuntime → container CLI
  • ContainerRuntimeSendable protocol; two implementations: ContainerCLIRuntime (real) and MockContainerRuntime (canned data for UI work without a live CLI).
  • ContainerCLIRuntime — shells out via ProcessRunner. Discovers the binary in order: UserDefaults override → /usr/local/bin/opt/homebrew/binwhich. Non-zero exits are mapped to typed errors (cliNotFound, systemNotRunning, commandFailed, decodingFailed); raw stderr is never shown in the UI.
  • ComposeRuntime / ContainerComposeCLIRuntime — separate seam for container-compose. Own binary discovery chain keyed by containerComposeCLIPath in UserDefaults. down is absent by design (see CLAUDE.md); stop/teardown uses ContainerRuntime.stop(id:) on matched containers.
  • ComposeFileParser — parses compose YAML with Yams (the app's first SwiftPM dependency) to get accurate service lists before first up. Lenient: unknown keys ignored, null service bodies allowed.
  • ComposeProjectStore — persists registered compose-file paths in UserDefaults (composeProjectPaths).
  • ProcessRunner — wraps Foundation.Process. Reads stdout and stderr concurrently to prevent pipe-buffer deadlocks; sets stdin to /dev/null so interactive prompts fail fast.
  • DTO layer (ContainerCLIModels.swift) — mirrors the CLI JSON with all-optional fields (tolerates unknown future keys), maps to flat UI models (ContainerSummary, ContainerStats). Container id doubles as name.
  • Statscontainer stats reports cumulative cpuUsageUsec; CPU % is derived from the delta between two samples in ContainersViewModel.mergeStats.
  • TerminalLauncher — opens a shell via osascript → Terminal.app. Validates the container id against ^[A-Za-z0-9._-]+$ before interpolation (injection defense).

Project layout

ContainerApp/
  ContainerAppApp.swift          # app entry point, scene declarations
  Models/                        # ContainerSummary, ContainerState, ContainerStats, …
  Runtime/                       # ContainerRuntime + ComposeRuntime protocols + CLI/mock implementations
  ViewModels/ContainersViewModel.swift
  Views/
    MenuBar/                     # popover views
    Dashboard/                   # window views (sidebar, table, detail panel, tabs, compose)
    Shared/                      # ErrorBannerView, EmptyStateView, state colours
  Utilities/                     # TerminalLauncher, ComposeFileParser, ComposeProjectStore
ContainerAppTests/               # Swift Testing suites
Fixtures/                        # captured real CLI output (backs decoder tests)
project.yml                      # XcodeGen spec — edit this, not the .xcodeproj

Contributing

  1. Edit project.yml for build-setting changes or new targets; run xcodegen generate afterwards.
  2. New source files under ContainerApp/ are picked up automatically by the target glob.
  3. When updating the CLI version, re-capture fixture output and update Fixtures/README.md.
  4. Known non-blocking issues are tracked in CURRENT_ISSUES.md.

License

ContainerApp is released under the MIT License.

ContainerApp is an independent, standalone UI application and is not affiliated with or endorsed by Apple. It interacts with Apple's container project (licensed under Apache License 2.0) solely by invoking the separately installed container CLI as an external process — no Apple container code is linked, embedded, or redistributed in this repository or in the built app, so its Apache 2.0 terms do not extend to this codebase. If a future release ever bundles the container binary inside the app, that release must ship Apple's Apache 2.0 license text and NOTICE file alongside it.

About

A native macOS menu-bar app and dashboard for Apple's container CLI

Topics

Resources

License

Stars

Watchers

Forks

Contributors

Languages