A fast, lean, open-source way to run Docker on Apple-silicon Macs.
100% Swift host · stock Docker Engine · custom kernel.org kernel · no Electron, no Go, no background daemons
Website · Benchmarks · Releases · Discord
Velox runs a real Docker Engine in a minimal Linux VM on Apple's
Virtualization.framework — and gets out of the way: a self-contained 226 MB
app that restarts to a ready Docker API in under two seconds and shrinks to
tens of megabytes when idle. Numbers below, methodology and a runnable harness
in docs/benchmarks.md.
- The stock
dockerCLI. No wrapper command — Velox registers a Docker context namedvelox, so Testcontainers and every Docker SDK just work, alongside any other Docker install.composeandbuildxare bundled: the app ships and links the two CLI plugins into~/.docker/cli-plugins, sodocker compose/docker buildxwork on a clean Mac with no Docker Desktop — and it never overwrites a compose/buildx you already have. - Reach containers by name at
<name>.velox.local— details below. - A native Mac app. Compose-grouped containers, images, volumes and networks with live CPU/MEM, streaming logs, a ⌘K command palette, a menu-bar quick panel, crash notifications, and one-click Reclaim Space.
- Nested virtualization (M3+, opt-in):
/dev/kvminside containers — run QEMU, Firecracker or Android emulators inside Docker. - Rosetta x86 (
--platform linux/amd64), VirtioFS bind mounts, the containerd image store (multi-platform images, attestations, Wasm). - Event-driven, never polling. Published ports come up the instant a container starts; a Resource Saver balloons an idle engine down to a sub-100 MB host footprint; the guest clock survives Mac sleep.
- Self-updating —
velox updateor the in-app Update button.
Requires macOS 15+ on Apple Silicon. Guest kernel, rootfs, engine and the
docker client are all bundled.
curl -fsSL https://raw.githubusercontent.com/mikaelhug/Velox/main/install.sh | bashOr manually: download the .zip from Releases,
drag Velox to Applications, and clear Gatekeeper's download quarantine
(Velox is signed but not notarized):
xattr -dr com.apple.quarantine /Applications/Velox.app && open /Applications/Velox.appFirst launch boots the engine, puts docker + velox on your PATH
(rootless symlinks), and registers the context:
docker context use velox
docker run --rm hello-worldThe app boots the engine automatically on launch and keeps it running while
open; velox start runs the same engine headless (no GUI needed).
Every container is reachable from the Mac at <name>.velox.local — its
real IP, any protocol, no -p required. Pure DNS + routing, no proxy in the
data path:
docker run -d --name web nginx
curl http://web.velox.local # no published port needed
docker run -d --name db -e POSTGRES_HOST_AUTH_METHOD=trust postgres
psql -h db.velox.local -U postgres # any protocol, not just HTTPCompose services resolve as <service>.<project>.velox.local. This needs a
one-time admin grant on first launch (a tiny root helper installs a route and
an /etc/resolver entry — control-plane only, it never touches connection
data); decline it and everything else still works.
Measured against Docker Desktop on the same Mac, the same way: both engines on
Virtualization.framework, identically configured (8 vCPU), one engine under
load at a time. Full methodology, fairness controls and a harness to reproduce
every number are in docs/benchmarks.md.
| Metric | Velox | Docker Desktop | vs Docker Desktop |
|---|---|---|---|
| Install footprint | 226 MB | 2,328 MB | 10× smaller |
| Idle RAM (host RSS, all processes) | ~0.9 GB | ~3.3 GB | 3.7× less |
| Startup (restart → API-ready, warm) | 1.74 s | 2.55 s | 1.5× faster |
Container launch (run --rm alpine true) |
104 ms | 160 ms | 1.5× faster |
| Network — container → host (iperf3) | 88.8 Gbit/s | 27.0 Gbit/s | 3.3× faster |
| Published port — host → container (4 streams) | 59.2 Gbit/s | 18.0 Gbit/s | 3.3× faster |
VirtioFS bind-mount write (dd 1 GiB) |
2,951 MB/s | 956 MB/s | 3.1× faster |
| Small-file extract (4,000 files → bind) | 0.21 s | 3.43 s | 16× faster |
Durable commit latency (fio --fsync, 4 K) |
0.31 ms | 0.47 ms | 1.5× faster |
| Container-overlay write | 1,695 MB/s | 1,217 MB/s | 1.4× faster |
Postgres pgbench TPS (8 clients, 30 s) |
13,318 | 11,690 | 1.14× faster |
| Cold image pull (381 MB on disk) | 19.1 s | 17.3 s | ~10% slower |
The idle-RAM row is the loaded benchmark baseline; an empty idle engine balloons further down, to a ~70 MB host footprint. Cold pull is the one path that trails (~10%), because durable layer extraction is fsync-heavy — the visible cost of the durability described below.
The data disk is durable by default: a raw image attached with
synchronizationMode: .fsync and guest barriers on, so every committed write
survives a crash, a power-off and in-place updates, at about 0.3 ms per
commit. Periodic fstrim returns freed space to macOS automatically.
- One Swift process hosts everything: VM lifecycle, the Docker-API VSOCK
proxy, port forwarding, DNS and the SwiftUI app. The only privileged piece
is a tiny optional root helper for
<1024ports and the named-access route. - Apple's in-kernel networking (VZNAT) is the container datapath — no userspace network stack anywhere.
- The kernel is built from kernel.org source:
tinyconfigplus a curated fragment, monolithic, tuned for fast container launch (HZ_1000, expedited RCU). - A tiny Rust
vinitis PID 1: one static musl binary does every boot step via direct syscalls — mounts, cgroups, clock, native DHCP, data disk, Rosetta — then supervises stockdockerdon a read-only, demand-paged erofs root. No LinuxKit, no initramfs, no dind. - Native-first: prefer what dockerd and the kernel already provide (e.g. dockerd 29's nftables backend — the legacy iptables packages are simply not shipped); anything genuinely custom is focused Rust.
Every version is pinned in one file (versions.env); CI builds a release on
every v* tag.
Velox coexists with VPNs — including full-tunnel WireGuard — with one known
interaction: some VPN clients (notably the OpenVPN-based AWS VPN Client)
silently switch off macOS's kernel packet forwarding when they connect, which
kills NAT egress for every VM on the Mac (vmnet-based Docker engines, UTM,
Internet Sharing…), not just Velox. The symptom is DNS resolving but every
container connection timing out. Velox detects the flip the moment it happens
(event-driven, no polling) and restores forwarding through its privileged
helper, so container networking keeps working with the VPN connected. If the
helper grant was declined, the engine log names the cause and the one-line
manual fix (sudo sysctl -w net.inet.ip.forwarding=1).
Apple's container validates the same
architecture (kernel.org kernel, tiny init, Swift on Virtualization.framework)
but speaks no Docker API — compose and Docker tooling don't work — and its
VM-per-container model can't share named volumes and pays a VM boot per
container. Velox is a Docker engine: one shared VM, the real API, and it
runs on macOS 15, not just 26.
Needs Docker (guest builds run in linux/arm64 containers) and a Swift 6
toolchain (Command Line Tools are enough).
./Scripts/build-kernel.sh # one-time: compile the kernel from source (long)
./Scripts/make-guest.sh # build the erofs rootfs; install to ~/.velox
./Scripts/build.sh # swift build -c release + ad-hoc codesign
./Scripts/build-app.sh # package a self-contained Velox.app
./Scripts/run.sh start # or: boot headless with a serial consoleThe signing entitlement is com.apple.security.virtualization only. Kernel
config lives in guest/kernel/velox.fragment; the guest filesystem in
guest/rootfs/Dockerfile.
Velox is licensed under the GNU Lesser General Public License, version 2.1 or
later (SPDX-License-Identifier: LGPL-2.1-or-later). See LICENSE
for the full text.
