Under the hood

Architecture Design

HoneyContainer is a native macOS app built with SwiftUI. Here's how it's structured internally—from the presentation layer down to credential storage.

Effective September 26, 2026

Overview

HoneyContainer follows a layered, modular architecture with clear separation between presentation, state management, business logic, and system integration. It targets macOS 26.0+ on Apple Silicon and is written in Swift 6.3.

Design goals: an intuitive multi-pane interface for complex container operations, non-blocking async execution, testable business logic via protocol abstraction, encrypted credential storage, and reactive state updates throughout the UI.

1. Architecture layers

The application is organized into five layers, each with a single responsibility. Data and commands flow down through the stack; state changes propagate back up through reactive bindings.

PRESENTATION        SwiftUI views (ContentView, SidebarView, DetailPanelView...)
      |  observes / updates
STATE MANAGEMENT    AppState, MenuPresentationState, @Published collections
      |  commands / mutations
BUSINESS LOGIC      Managers & services (AWS credential proxy, CLI runners...)
      |  orchestrates / executes
INFRASTRUCTURE      CLIProcessRunner, Logger, FileManager, Network APIs
      |  reads / writes
DATA                UserDefaults, Keychain, FileSystem, SecureStorage
LayerResponsibilityKey components
PresentationRender UI, capture input. Pure SwiftUI—no business logic.ContentView, SidebarView, CenterPanelView, DetailPanelView, feature views
State managementSingle source of truth, reactive updates, thread-safe on the main actor.AppState, MenuPresentationState
Business logicContainer operations and workflows behind protocol seams for testability.AWS credential proxy, per-runtime CLI wrappers (ContainerCLI, DockerCLI, PodmanCLI, KubectlCLI), build orchestration
InfrastructureLow-level system access: processes, logging, files, networking.CLIProcessRunner, Logger, SecureStorage
DataPersistence and encrypted credential storage.UserDefaults, Keychain, FileSystem

2. Component structure

Entry point and top-level composition:

HoneyContainerApp (@main)
    -> creates/injects AppState (@StateObject)
        -> ContentView
            +- SidebarView (navigation)
            +- CenterPanelView (primary content)
            +- DetailPanelView (secondary detail)

Feature areas are organized around the same pattern of views paired with business logic:

  • Containers & images — ImagesListView, ImageDetailView, a unified ContainersListView tree covering Apple container, Docker, Podman, and Kubernetes rows, ImageBuildLogViewer, and KubectlApplyView for manifest apply/diff
  • Logging & monitoring — LogView, StatsView, metrics polling and log streaming
  • AWS integration — AWSProfilesView, AWSCredentialsNewView, AWSLaunchOptionsView, backed by AWSCredentialProxyHelperManager
  • Registry management — RegistryView, RegistryLoginView, credential caching and authentication
  • Execution environment — EnvView, EnvListView, CommandView, environment variable management
  • Remote host connectivity — RuntimesView for local and remote runtimes, RemoteHostNewView, remote system diagnostics, and RemoteHostStore/RemoteHostTrustStore, backed by SSH/SFTP via Citadel with host fingerprint verification

3. Key design patterns

PatternHow it shows up
MVVMAppState acts as the view model: @Published properties plus methods that mutate state; SwiftUI views observe and render.
Observer (Combine)User Action -> AppState method -> mutate @Published property -> SwiftUI re-renders.
Protocol-based dependency injectionSeams like CLIProcessRunning let RealCLIProcessRunner ship in production and FakeCLIProcessRunner stand in for tests.
SingletonsShared resources such as Logger.shared and the AWS credential proxy manager owned by AppState.
Main actor isolationAppState is @MainActor, so all published-state mutations are guaranteed to happen on the main thread.
Error translationInfrastructure errors (e.g. a CLI timeout) are translated into application-level errors like ContainerCommandError before reaching the UI.
Structured concurrencyAsync work runs in `.task {}` or `Task {}` blocks rather than callbacks or manual thread management.

4. Data flow

A typical user action, such as deleting a container, flows straight down the layer stack and back:

User clicks "Delete Container"
  -> View calls appState.deleteContainer(id:)
  -> AppState: set isLoading, optimistically update collection
  -> Business logic formats CLI arguments, calls CLIProcessRunner
  -> Infrastructure spawns Foundation.Process, captures stdout/stderr
  -> Result returns up the stack
  -> AppState updates @Published state (or restores it on error)
  -> SwiftUI re-renders affected views

Real-time data such as container stats and logs use a polling loop instead of a one-shot round trip:

Task { @MainActor in
    while !Task.isCancelled {
        let containers = try await fetchContainers()
        self.containers = containers
        try await Task.sleep(for: .seconds(2))
    }
}

5. State & UI architecture

AppState is the central, @MainActor-isolated state container. It loads configuration from UserDefaults on init, publishes changes via Combine, starts/stops background refresh tasks, and captures errors for presentation to the user.

The UI is a menu-driven multi-pane layout, not a navigation stack: a persistent sidebar selects a SidebarItem, the center panel switches on that selection, and an optional detail panel shows contextual information. Views never talk to each other directly—they read and write through AppState, and SwiftUI’s onChange bindings keep everything in sync.

6. System integration

  • Process execution — CLIProcessRunner spawns Foundation.Process to drive the container, docker, podman, and kubectlCLIs, AWS CLI calls, and image builds. HoneyContainer talks to Apple’s container runtime exclusively through the container binary; the earlier in-process API client mode and its package dependencies have been removed.
  • File system access — app caches and preferences, user-selected directories via NSOpenPanel, and the Docker socket at /var/run/docker.sock.
  • Network access — outbound-only client connections for registry pulls/pushes, AWS API calls, and remote SSH hosts via Citadel.
  • Keychain — AWS access keys, registry credentials, SSH keys, and symmetric keys are stored via a KeychainSymmetricKeyStore abstraction.
  • Logging — unified logging to the console and rolling log files in ~/Library/Caches/, with ISO 8601 timestamps.

7. Multi-runtime container sources

The unified Containers tree merges rows from four independent CLI-backed sources: Apple container, Docker, Podman, and Kubernetes. Each runtime instance selects its source, and each source has its own wrapper (ContainerCLI, DockerCLI, PodmanCLI, KubectlCLI) that shells out to its binary and maps the output to a shared ContainerRow model.

Apple container / Docker   container               (flat)
Podman                     pod -> pod member       (one level)
Kubernetes                 namespace -> pod -> pod member (two levels)
  • Podman has a first-class pod concept with no Docker equivalent. pod ps and ps -a run concurrently and are merged into a tree of pods and their member containers.
  • Kuberneteshas no independently listable container resource, so a pod’s containers are synthesized from kubectl get pods --all-namespaces -o json and nested under namespaces and pods.
  • Row IDsare prefixed per source so they can’t collide across runtimes, and so a Kubernetes pod’s or container’s namespace and name can be recovered from the ID alone.
  • Remote hosts follow the same split: each host dispatches to the CLI that matches its configured runtime.

Kubernetes manifest apply — KubectlApplyView lets you paste or pick YAML/JSON manifests and run a server-side dry-run, kubectl diff, or a real kubectl apply, all namespace-scoped. Syntax is checked client-side first with Yams; since YAML 1.2 is a JSON superset, one parser covers both formats.

8. Concurrency model

HoneyContainer uses Swift’s structured concurrency throughout: .task {} for view-attached work, explicit Task {} for background operations, and @MainActor isolation to enforce that UI-affecting state only ever mutates on the main thread. Sendable types (e.g. CLICommandResult) keep data safe as it crosses thread boundaries, and background polling loops check Task.isCancelled so they shut down cleanly when the app terminates.

9. Security architecture

  • Credential storage— AWS keys and registry credentials are stored as Keychain generic password items, scoped to the app’s Team ID and Bundle ID, and are never written to disk unencrypted. A single master key in the Keychain (one permission prompt on first launch) derives purpose-specific sub-keys via HKDF-SHA256.
  • Secure communication — HTTPS via swift-nio-ssl and the macOS Security framework; SSH via Citadel; certificate validation via Swift Certificates.
  • Input validation — form validators sanitize file paths, image names, environment variables, network subnets, volume driver options, and labels before they reach a CLI invocation.

For the full security policy, including vulnerability reporting, see the Security Policy.

10. Extension points

The layered design makes it straightforward to add new functionality without touching unrelated code:

  • New container operations (e.g. pausing a container) add a method and published property to AppState, a button in the relevant view, and a CLI call in the business logic layer.
  • New registry types implement a RegistryProvider protocol and register a matching view in RegistryView.
  • Telemetry can be added behind a protocol seam and injected into AppState without changing existing call sites.

11. Architecture decisions

DecisionRationale
SwiftUI over AppKitModern toolkit, better composition, easier maintenance.
Centralized AppStateSingle source of truth reduces bugs and simplifies debugging.
Protocol abstraction for CLI callsEnables testing without a real Docker or container runtime.
Main actor isolationPrevents data races and enforces correct threading by construction.
UserDefaults for configurationLightweight, persists across app updates in the same domain.
Keychain for secretsThe industry-standard, encrypted-at-rest option on macOS.

Related pages

Want the feature-level view instead of the internals? See the full feature list, the software bill of materials, or the security policy.