Here's a confession: I don't use WSL.
I don't open an Ubuntu shell. I don't run Linux tools on Windows.
The only reason WSL was on my laptop was Docker.
And yet it was the heaviest thing on my machine.
I'd stop every container. docker ps was empty.
vmmem was still sitting on 2 GB of my RAM.
I'd delete 5 GB of images to make room.
My disk got none of it back.
Bind-mounted projects were slow.
And Docker Desktop had a steady supply of small bugs:
the kind you fix by restarting it, then restarting it again.
None of it was a bug I could report and wait out.
Docker Desktop runs on WSL 2, and WSL 2 is a whole
general-purpose Linux VM. I was paying for all of it
to use one thing.
So I asked a dumb question:
what if the VM only had to run Docker?
The problem
If you run Linux containers on Windows, you know the drill:
-
vmmemkeeps a gigabyte or two long after your containers stop - you delete images and the VHDX never shrinks
- a project bind-mounted from
C:\is slow - at a company with 250+ people, Docker Desktop needs a paid plan
I didn't want a better Docker Desktop.
I wanted something smaller.
What I built
hyperbutler: a Docker engine for Windows 11. No WSL.
It boots a tiny Linux VM directly through the Windows
Host Compute Service (the same API WSL 2 uses underneath),
runs dockerd inside it, and gives you the normal Docker API
on a named pipe.
hyperbutler start
docker run --rm hello-world
docker compose up -d
hyperbutler stop
You keep the stock docker CLI and Compose. That's the whole point.
Same laptop, both with default settings:
| hyperbutler | Docker Desktop (WSL 2) | |
|---|---|---|
| Start until a container runs | 2.5 s | 5.6 s |
Bind mount from C:\: read 2000 small files |
0.4 s | 3.4 s |
| Idle memory, VM running | ~320 MiB | 1.7–2.7 GB |
| Disk after deleting ~5 GB of images | 1.5 GiB, given back | 6.8 GiB, kept |
| Installed size | 179 MB | ~6.7 GB |
| Needs WSL | no | yes |
One machine, one build of each. Not a general result.
The full method and raw CSVs are in the repo.
How it works
Three pieces, and each gets only the privilege its job needs:
docker CLI / Compose
| named pipe
v
hyperbutler engine <- runs as you, no admin
Docker API relay, network, file sharing
| |
| start / stop | Hyper-V sockets
v |
hyperbutler broker <- tiny Windows service,
the only part that talks to HCS | the only privileged piece
| |
v v
Linux VM on Hyper-V (no WSL)
dockerd + containerd
read-only, hash-pinned system disk
data disk that gives freed space back
-
The engine is a normal process running as you.
It relays the Docker API into the VM, runs the VM's network
in user mode (no virtual switch, no vEthernet adapter),
and serves your
C:\folders to containers. - The VM is a minimal Alpine userland that boots in about a second. Its system disk is read-only and checked by hash. Its data disk hands deleted blocks back to Windows, which is why the disk row above says 1.5 GiB, not 6.8.
- The broker is the only part with privileges. More on that below, because it's the part I worried about most.
Written in Go, end to end: the engine, the broker and the agent inside the VM.
The 2 MiB holes
The memory number didn't come for free.
My first version had the same problem as everyone else:
after a workload, the VM wouldn't give memory back.
Not all of it. Just a little more every run.
Linux said the memory was free.
Windows said the VM was still using it.
Both were right.
The Hyper-V balloon driver only reports free memory back
to the host in 2 MiB chunks. After a workload, about
70 MiB was free in smaller pieces. Free to Linux.
Invisible to Windows. Growing with every run.
Think of a parking lot where the attendant only counts
a space as free if ten in a row are empty. Scatter a few
empty spaces around, and the lot looks full forever.
One kernel parameter fixed it:
page_reporting.page_reporting_order=0
After that, the VM went back to idle after every fill
(+84 MiB down to about 0).
If you've ever wondered why vmmem never quite comes
back down, this is one of the reasons.
The phone call that was 20x faster
The second surprise was the network.
All container traffic crosses one Hyper-V socket between
Windows and the VM. Downloads into a container topped out
at 2.7 Gbit/s, and the profiler said everything was idle.
It turns out Windows sends slowly on a Hyper-V socket it
accepted: about 1 ms per ~600 KB.
On one it dialed: about 50 µs.
Same socket type. Same data. Different caller.
I couldn't find this documented anywhere.
So I flipped it: the VM listens, Windows dials.
Plus segmentation offload. 2.7 → 10.2 Gbit/s.
The part I'm most careful about
Windows only lets administrators touch HCS.
The easy fix is "add yourself to Hyper-V Administrators."
I didn't want that. That group has unrestricted control
over Hyper-V, and every program you run inherits it.
So the installer adds one tiny Windows service instead.
It's the only privileged piece, and it was designed
backwards from the bugs that broke similar tools before:
- no paths, no command lines, no JSON: seven typed requests
- it learns who you are from the pipe, not from the message
- it only boots an image whose hash is compiled into it
- when your connection closes, your VM dies with it
Files, ports and the Docker API never run there.
They stay in your own unprivileged process.
Where it stands, honestly
- Preview. v0.1.0-preview.2 is out. v0.1.0 stable waits on a 24-hour soak.
- Windows 11 only, x64. Home, Pro, Enterprise.
- Unsigned for now. SmartScreen will complain. Check the SHA256SUMS.
- Linux containers + CLI + Compose. That's it. No Kubernetes, no Windows containers.
- Side by side is fine. Docker Desktop can stay installed.
Try it
Download the MSI from
Releases, then:
hyperbutler start
docker run --rm hello-world
If something breaks, hyperbutler doctor tells you what
your PC is missing.
GitHub: https://github.com/Higangssh/hyperbutler
What I need most is machines that aren't mine.
Genuine question: what's the most RAM you've caught
vmmem holding with nothing running? I'd love to compare.
Top comments (1)
성능표에 한 머신·한 빌드의 결과라고 범위를 적고 원시 CSV를 연결해 둔 점이 좋습니다. 24시간 soak에서는 반복적인 compose up/down 뒤 유휴 메모리뿐 아니라 절전 복귀와 broker 재시작 후 기존 볼륨이 유지되는지도 별도 항목으로 보여주시면, 일회성 속도와 장기 사용 안정성을 구분하기 좋겠어요.