How it works
What Kuvo runs on your Mac, and how the docker command reaches the engine inside the VM.
Docker containers need a Linux kernel, and macOS doesn’t have one. Like every Docker app for the Mac, Kuvo runs Linux in a virtual machine. What’s different is how little sits around it.
The pieces
your Mac Linux VM (Alpine)
┌──────────────────────────────────┐ ┌──────────────────────┐
│ docker CLI ─┐ │ │ │
│ kuvo CLI ───┼─▶ ~/.kuvo/docker.sock ── vsock ──▶ socat ─▶ dockerd │
│ Kuvo.app ───┘ │ │ │ │
│ │ │ containers │
│ localhost:8080 ◀── port bridge ──┼── NAT ─────┼────────────┘ │
│ /Users ──────────────────────────┼─ virtiofs ─▶ /Users │
└──────────────────────────────────┘ └──────────────────────┘
There are two processes on your Mac: Kuvo itself, and the virtual machine, which macOS runs in a separate process for it.
The virtual machine
Kuvo uses Apple’s Virtualization framework, the same one behind most modern Mac VM apps. There’s no QEMU and no bundled hypervisor.
- Linux. Kuvo boots the official Alpine Linux kernel and installs Alpine onto a disk image on first launch. Alpine is small and boots quickly.
- Docker. The engine is the
dockerpackage from Alpine’s repository: the realdockerd,containerdandrunc, not a reimplementation. - Size. The VM gets half your CPU cores (at least two) and half your memory, up to 8 GB. The VM only takes memory from your Mac as it uses it.
- Disk. A sparse 64 GB disk image holds Alpine, Docker, and all your images, containers and volumes.
- Rosetta. If Rosetta is installed, Kuvo shares it with the VM and registers it for x86-64 binaries, so
linux/amd64images run without CPU emulation.
The boot scripts that run inside the VM are plain shell, kept short so they’re easy to read and audit. They live in GuestScripts.swift.
Reaching the engine
dockerd listens on its usual Unix socket inside the VM. A small relay (socat) exposes it on a vsock port, a direct channel between the VM and the Mac that doesn’t go over the network. Kuvo connects that channel to ~/.kuvo/docker.sock on your Mac.
The docker command, the kuvo command and the app itself all talk to that one socket, using Docker’s standard Engine API.
Ports
The VM has its own private network address behind NAT. When a container publishes a port, Kuvo sees it in the container list and opens the same port on 127.0.0.1, relaying each connection to the VM. When the container stops, the port closes again.
Files
Two folders are shared into the VM with virtiofs:
/Users, mounted at the same path, so bind mounts like-v $PWD:/appwork without translating paths.- Kuvo’s own folder, used to hand the VM its boot scripts and kernel modules, and for the VM to hand back its network address and Docker’s log.
Starting and stopping
When you open Kuvo, it boots the VM and waits until Docker answers on the socket. That usually takes about three seconds.
When you quit, Kuvo asks the VM to shut down cleanly: Docker stops its containers, the disk is synced, and the VM powers off. If it hasn’t finished within 15 seconds, Kuvo stops it.
The app
Kuvo is written in Swift with SwiftUI and AppKit. There’s no web view. It speaks HTTP to the Docker socket directly with Apple’s Network framework, and draws live graphs and animations with Core Animation, so they keep running without redrawing the rest of the window.
The program, including the kuvo command, is a single 2.5 MB binary. It has two dependencies: swift-argument-parser and SwiftTerm.
Not Apple’s containerization
macOS 26 includes Apple’s own container framework, which runs each container in its own lightweight VM. Kuvo doesn’t use it. Kuvo runs one VM with the full Docker Engine, so Compose, networks, volumes, BuildKit and the rest of the Docker API work exactly as they do on Linux.