<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Couq</title>
    <description>The latest articles on DEV Community by Couq (@couq).</description>
    <link>https://dev.to/couq</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F4164068%2F1e39071f-c61e-4914-bf86-73ba6464ccfd.png</url>
      <title>DEV Community: Couq</title>
      <link>https://dev.to/couq</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/couq"/>
    <language>en</language>
    <item>
      <title>I built a ~8 MB native Docker app for Apple Silicon Macs — here's how it works</title>
      <dc:creator>Couq</dc:creator>
      <pubDate>Mon, 05 Oct 2026 13:53:48 +0000</pubDate>
      <link>https://dev.to/couq/i-built-a-8-mb-native-docker-app-for-apple-silicon-macs-heres-how-it-works-337o</link>
      <guid>https://dev.to/couq/i-built-a-8-mb-native-docker-app-for-apple-silicon-macs-heres-how-it-works-337o</guid>
      <description>&lt;p&gt;I'm the developer of &lt;a href="https://github.com/nextage-soft/dockz" rel="noopener noreferrer"&gt;DockZ&lt;/a&gt;, a free, Apache-2.0 Docker app for Apple Silicon Macs. Two things pushed me to build it:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Licensing.&lt;/strong&gt; Docker Desktop needs a paid subscription for commercial use at larger companies. I wanted something I could use anywhere without thinking about seats or license terms.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Weight.&lt;/strong&gt; Docker Desktop felt heavy on my Mac — a ~1.5 GB app and a lot of background activity just to run a few containers. I wanted something much lighter that still has a real GUI, not just a CLI.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;The result is a ~8 MB SwiftUI app (3.5 MB DMG). This post is about how it works under the hood and a few problems I didn't expect.&lt;/p&gt;

&lt;h2&gt;
  
  
  The architecture in one picture
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt; macOS                                    Alpine VM (Virtualization.framework)
┌─────────────────────────────┐          ┌───────────────────────────────┐
│ DockZ (SwiftUI)             │  vsock   │ dockerd (upstream, unmodified)│
│  ├─ dashboard / monitor     │◀────────▶│ containerd, BuildKit          │
│  ├─ port forwarder          │          │                               │
│  └─ docker context "dockz"  │          └───────────────────────────────┘
└─────────────────────────────┘
        ▲
        │  docker / docker compose / buildx (unchanged)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;No fork of Docker.&lt;/strong&gt; The VM runs the real upstream &lt;code&gt;dockerd&lt;/code&gt;. DockZ registers a normal Docker context called &lt;code&gt;dockz&lt;/code&gt;, so &lt;code&gt;docker&lt;/code&gt;, Compose and BuildKit/buildx work exactly as they do anywhere else.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;vsock instead of a virtual network for control traffic.&lt;/strong&gt; The dashboard talks to the Docker Engine API over virtio-vsock — plain HTTP/1.1 per stream, no TCP port exposed on the Mac.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Automatic port forwarding.&lt;/strong&gt; DockZ watches Docker's events API; when a container publishes a port, it's mirrored to the same port on the Mac and bound to the address Docker reports (so the default &lt;code&gt;0.0.0.0&lt;/code&gt; really is reachable from your LAN).&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Because the heavy lifting is done by Apple's hypervisor and a stock Linux userland, the app itself stays small: no bundled Electron runtime, and the guest image is minimal Alpine.&lt;/p&gt;

&lt;h2&gt;
  
  
  Managing remote Docker engines — with keys that never leave the chip
&lt;/h2&gt;

&lt;p&gt;The same window can manage other Docker engines: a server over SSH, a server over mutual TLS, or another engine's socket on the same Mac (Colima, OrbStack, Docker Desktop).&lt;/p&gt;

&lt;p&gt;SSH was the easy part: DockZ runs &lt;code&gt;docker system dial-stdio&lt;/code&gt; on the remote host and speaks the Engine API over that pipe.&lt;/p&gt;

&lt;p&gt;TLS was more interesting. The usual setup leaves a &lt;code&gt;key.pem&lt;/code&gt; sitting in &lt;code&gt;~/.docker&lt;/code&gt; — if someone gets your Mac's files, they get root-equivalent access to your servers. I wanted the client key to be &lt;strong&gt;unextractable&lt;/strong&gt;:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;The private key is created inside the Mac's &lt;strong&gt;Secure Enclave&lt;/strong&gt; (CryptoKit's &lt;code&gt;SecureEnclave.P256.Signing.PrivateKey&lt;/code&gt;), protected with Touch ID / user presence.&lt;/li&gt;
&lt;li&gt;DockZ builds a PKCS#10 &lt;strong&gt;certificate signing request&lt;/strong&gt; from it; only the CSR leaves the Mac. You sign it with your CA and paste the certificate back.&lt;/li&gt;
&lt;li&gt;Apple's Network.framework only accepts Keychain identities for TLS client auth, so DockZ uses &lt;strong&gt;swift-nio-ssl&lt;/strong&gt; with a custom signing callback: during the handshake, NIO asks DockZ to sign, and the Secure Enclave does it.&lt;/li&gt;
&lt;li&gt;The &lt;code&gt;docker&lt;/code&gt; CLI can't use a Secure Enclave key, so DockZ exposes a &lt;strong&gt;local relay socket&lt;/strong&gt; per environment. The CLI talks plain HTTP to the socket; DockZ adds the TLS. The CLI never sees a key.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The session unlocks with Touch ID and relocks when the screen locks, the Mac sleeps, or you switch away.&lt;/p&gt;

&lt;p&gt;One gotcha worth sharing: &lt;code&gt;docker exec&lt;/code&gt; and &lt;code&gt;attach&lt;/code&gt; rely on &lt;strong&gt;TCP half-close&lt;/strong&gt; — the client closes its write side and keeps reading. My first relay closed both directions as soon as one side finished, which silently truncated output. Propagating half-close correctly (allowing remote half-closure, then closing only the output side after pending writes flush) fixed it.&lt;/p&gt;

&lt;h2&gt;
  
  
  When containers "disappeared": making the VM heal itself
&lt;/h2&gt;

&lt;p&gt;Early on I hit a nasty one: every few days, all containers seemed to vanish from the dashboard. The console log showed the guest kernel had hit an &lt;strong&gt;oops in the virtio-vsock transmit path&lt;/strong&gt;, after which ext4 deadlocked and &lt;code&gt;dockerd&lt;/code&gt; stopped answering — the VM was technically "running" but useless, sometimes for hours.&lt;/p&gt;

&lt;p&gt;I still haven't pinned down whether the root cause is in the guest driver or the host side. So DockZ now treats the VM like a supervised service:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Fail loudly instead of hanging.&lt;/strong&gt; The guest runs with &lt;code&gt;panic_on_oops=1&lt;/code&gt;, &lt;code&gt;softlockup_panic=1&lt;/code&gt; and &lt;code&gt;panic=10&lt;/code&gt;, so a broken kernel reboots instead of lingering half-alive.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Health-check the engine.&lt;/strong&gt; DockZ pings &lt;code&gt;/_ping&lt;/code&gt; every 15 seconds; after 4 failures spanning at least 60 seconds of uptime (so a sleeping Mac doesn't count), the engine is declared unresponsive and the VM is restarted.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Crash-loop protection.&lt;/strong&gt; At most 3 automatic restarts per 10 minutes; after that DockZ stops and tells you instead of looping forever.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Keep the evidence.&lt;/strong&gt; Console logs of the last 5 boots are rotated and kept, so the next oops can actually be investigated.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;I also cut down vsock churn: the Monitor tab used to poll container stats, opening a new connection per container every few seconds. It now keeps one long-lived stats stream per container, and the dashboard refreshes on Docker events instead of polling every 4 seconds.&lt;/p&gt;

&lt;h2&gt;
  
  
  A small bug that crashed the app: Docker's &lt;code&gt;-1&lt;/code&gt;
&lt;/h2&gt;

&lt;p&gt;The Monitor tab shows disk usage per image, volume and build cache. Docker reports &lt;strong&gt;&lt;code&gt;-1&lt;/code&gt;&lt;/strong&gt; when a size is unknown. Decoding that into an unsigned integer wrapped it to &lt;code&gt;UInt64.max&lt;/code&gt;, and the arithmetic downstream crashed. Now every byte count from Docker goes through one helper that turns negative values into "unknown". Small lesson: when you consume someone else's JSON, sentinel values deserve a single choke point, not scattered checks.&lt;/p&gt;

&lt;h2&gt;
  
  
  What else is in there
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;A native dashboard: containers grouped by Compose stack with crashes and failing health checks flagged, images, volumes, networks, registries, live logs, and filters plus search on every list.&lt;/li&gt;
&lt;li&gt;A Monitor tab with live CPU, memory, network and disk per container, VM vitals, and cleanup of unused images, volumes and build cache.&lt;/li&gt;
&lt;li&gt;Multipass-style Linux machines (Alpine/Debian/Ubuntu), one-click multi-node k3s/kubeadm templates, and APFS copy-on-write snapshots of VM disks.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Limitations (honestly)
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Apple Silicon and macOS 15+ only.&lt;/li&gt;
&lt;li&gt;It's a young project.&lt;/li&gt;
&lt;li&gt;Releases aren't notarized yet, so macOS asks you to allow the app once (System Settings → Privacy &amp;amp; Security → &lt;strong&gt;Open Anyway&lt;/strong&gt;).&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Try it
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;brew tap nextage-soft/dockz https://github.com/nextage-soft/dockz
brew &lt;span class="nb"&gt;install&lt;/span&gt; &lt;span class="nt"&gt;--cask&lt;/span&gt; nextage-soft/dockz/dockz
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Or download the DMG from &lt;a href="https://github.com/nextage-soft/dockz/releases/latest" rel="noopener noreferrer"&gt;GitHub Releases&lt;/a&gt;.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;GitHub: &lt;a href="https://github.com/nextage-soft/dockz" rel="noopener noreferrer"&gt;https://github.com/nextage-soft/dockz&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Website (with comparisons against Docker Desktop, OrbStack and Colima): &lt;a href="https://dockz.nextagesoft.com" rel="noopener noreferrer"&gt;https://dockz.nextagesoft.com&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;It's actively under development. If you use Docker Desktop, OrbStack or Colima daily, I'd really like to hear what feels broken, missing or unnecessarily complicated — and if you've seen vsock trouble on Virtualization.framework, I'd love to compare notes in the comments.&lt;/p&gt;

</description>
      <category>docker</category>
      <category>macos</category>
      <category>swift</category>
      <category>opensource</category>
    </item>
  </channel>
</rss>
