Since 2021, Docker Desktop has needed a paid subscription at companies past a certain size. A lot of developers have been told to stop using it, and the honest replacement list is shorter than it looks.
Here is the thing that took me embarrassingly long to internalize: the engine was never the licensed part. dockerd is Apache-2.0. It runs perfectly well in WSL2. What Docker Desktop sells you is the desktop application around it — the GUI, the VM management, the update channel, the support.
If you do not want any of that, the remaining work is smaller than it sounds. You need the engine in a distro, and you need to serve its API on \\.\pipe\docker_engine — the pipe docker.exe already talks to. Get that right and Compose, buildx, Testcontainers, Dev Containers and everything else keep working without knowing anything changed.
That is what Skrog is. No Electron, no Kubernetes, no tray icon you have to keep running. Install it once and docker ps works forever.
irm https://wslkit.github.io/skrog/install.ps1 | iex
skrog install
docker run --rm hello-world
Under five minutes on a clean Windows 11 box, and you never touch WSL directly.
Lifecycle. WSL2 is a VM that disappears. wsl --shutdown kills it. Sleep/resume breaks it. The engine crashes. If any of those leave docker broken until the user runs something by hand, you have built a demo, not a tool. Skrog runs a supervisor that starts at logon, watches the engine, and is itself watched — if the supervisor dies, a small launcher restarts it in about a second.
Idle RAM. The most common complaint about every WSL2-based engine is that vmmem sits there holding a gigabyte while you are not using Docker. skrog config set idle-timeout 30m stops a quiet engine and cold-starts it on your next docker command.
The timely bit: Microsoft now ships a container runtime, and you still can't reach it
If you have updated WSL recently, you already have wslc.exe — Microsoft's own container runtime, shipping in WSL 2.9.3+ and headed for GA. It is the strongest argument against this project existing, so it is worth being precise about.
Every wslc session really does run a genuine Moby engine. I went and checked: stock dockerd on a unix socket inside the session VM. Real engine, not a reimplementation.
But it is started with no -H flag. No named pipe, no TCP port, no inbound route from Windows. The engine is complete and unreachable.
That means anything that speaks the Docker API cannot talk to it: Compose, Testcontainers, Dev Containers, buildx, act, Dagger, anything that mounts docker.sock. wslc mimics the docker CLI, which covers build and run and stops there.
That gap — a real engine with no endpoint — is the entire reason this project has a reason to exist. Skrog serves the endpoint. It can even serve wslc's engine, if you would rather Microsoft owned the runtime: skrog install --engine wslc.
So which one is actually faster?
I benchmarked both on one host, because "we're fast" is not a claim, it is a vibe.
Windows 10 Pro 22H2 (19045), WSL 2.9.11, 4 vCPU / 7.8 GB visible to each VM. Skrog with engine 29.8.1 on its vsock transport, /mnt/c on 9p; a wslc session running the dockerd Microsoft ships. Control-plane timings are wall-clock around each shipped CLI, so process spawn is included on both sides; filesystem numbers are timed inside the container, so they are not.
Control plane — wslc wins most of it, and it should:
| wslc | Skrog | |
|---|---|---|
| list containers, median of 10 | 53 ms | 74 ms |
| list images, median of 10 | 57 ms | 276 ms |
exec <running> true, median of 10 |
109 ms | 155 ms |
run --rm alpine true warm, median of 5 |
538 ms | 528 ms |
docker version round trip |
— | 89 ms |
That ordering is architecture, not effort. wslc goes COM-to-VM with no bridge in the path; Skrog serves a named pipe and relays over vsock. Twenty to forty milliseconds of the gap is the bridge, and I would rather pay it than not have an endpoint. Actually running a container — the thing you do all day — is a tie.
The 276 ms for list images is the outlier, and I assumed it was my bug. It is worth showing how that went, because the instinct was wrong.
First guess: payload size. /images/json returns 7 838 bytes on this machine and /containers/json returns 7 052. Same order. Not that.
Second guess: something in my bridge doing per-image work — there is a reference parser in that path for admission control. So I cut the bridge out entirely and hit the engine's unix socket from a container, 20 requests each:
20x /containers/json real 0m 0.97s # 48 ms each
20x /images/json real 0m 3.20s # 160 ms each
Same 3.3× ratio, no Windows, no named pipe, no vsock, no Skrog code in the path at all. It is dockerd computing image sizes, and it would cost the same in Docker Desktop. wslc is quicker here because it asks a different, cheaper question, not because its plumbing is better.
Windows-folder bind mounts — wslc wins, because it mounts them over virtiofs while a WSL2 distro defaults to 9p:
| wslc (virtiofs) | Skrog (9p) | |
|---|---|---|
| read 256 MB | 760 MB/s | 192 MB/s |
| write 256 MB | 155 MB/s | 103 MB/s |
| create 1000 files | 1.59 s | 2.39 s |
ls -l 1000 files |
0.24 s | 0.73 s |
| read 1000 files | 1.37 s | 2.65 s |
It is also closeable without me writing any code: WSL 2.9 will give a distro virtiofs too, behind a .wslconfig key. skrog config set wsl.virtiofs true and a wsl --shutdown puts Skrog at 811 MB/s read and 0.22 s for ls -l — level with wslc, and 4× its own 9p. (Those two figures come from a separate run documented here; flipping the key needs a wsl --shutdown, which stops every distro on the machine, so I did not do it mid-benchmark.)
Named volumes — where real workloads actually keep their data, Skrog wins:
| wslc | Skrog | |
|---|---|---|
| write 256 MB | 1.5 GB/s | 1.7-2.1 GB/s |
| read 256 MB | 8.6 GB/s | 7.9-8.8 GB/s |
| create 1000 files | 0.07 s | 0.04 s |
Published ports — wslc wins: 574-647 MB/s against 380-416 MB/s on a 256 MB download, and 2.8 ms against 4.8 ms on a small GET, averaged over 20.
Memory — the architectural one. A wslc session is its own VM, and I measured ~750 MB of working set for it. Skrog's engine lives in the WSL2 utility VM you already have, next to your Ubuntu, so it adds an engine (451 MB used inside the guest, against the session's 230 MB) rather than a second VM. Windows-side, Skrog is 23 MB plus a 6 MB watchdog.
If you already run WSL, that is the difference between one VM and two.
The summary I would give a colleague: wslc is quicker on control-plane calls, published ports and cold start; Skrog is quicker on named volumes and cheaper on memory; bind mounts go to wslc unless you flip one config key, and then they tie. If those were the only differences you should probably take the first-party option. They are not the only difference — the difference is whether docker compose up works at all.
Things Skrog does that Docker Desktop does not
Dropping the desktop app is usually framed as a sacrifice. Some of it is — see the next section. But a headless engine you own outright can do things a managed desktop application will not, and these are the ones people actually end up using.
Pin the engine, exactly. skrog lock writes the precise dockerd, containerd, runc and BuildKit commits to a file you commit. setup-skrog installs that same file on your runner, and skrog install --locked installs it on a laptop. "Works locally, fails in CI" caused by engine drift stops being a category of bug. Docker Desktop ships the version it ships and updates on its own schedule — which is the same complaint from the other direction: nothing here auto-updates, ever, and there is no telemetry. Your engine changes when you change it.
See what actually talked to your engine. skrog audit tail logs container-affecting API calls, because Skrog is already in the request path. skrog audit trace -- <command> goes further and shows you what a given pipeline did — useful the first time a build tool creates a container you did not expect.
Refuse things before the engine sees them. A small, fixed rule vocabulary in policy.yaml:
deny-privileged: true
deny-host-namespaces: true
allow-registries: [registry.example.com, "*.internal"]
require-digest: true
allow-bind-sources: [C:\work]
Same position in the request path, so the rules apply to Compose, Testcontainers and your IDE without any of them knowing. It is not a security boundary against someone who owns the machine — they can edit the file or point DOCKER_HOST elsewhere — and the docs say so plainly. It catches mistakes, which is most of the value of writing a policy down.
Snapshot the whole engine, and reset to it. skrog snapshot save golden captures images, containers and volumes; skrog reset --to golden puts them back unconditionally. On a CI runner that is a clean, pre-warmed engine per job in seconds rather than a re-pull.
Treat the install as configuration. skrog install --config skrog.yaml is idempotent and converges a machine to a described state; skrog config export writes that file out of a machine you already like. Lifecycle hooks (post-start, pre-stop, on-idle-stop, on-wake) let you hang your own scripts off engine events.
Hand the engine to someone else. skrog serve --tcp exposes it over mutual TLS, reachable only by holders of a client certificate this machine's CA signed. Off by default. On the other end, skrog remote add and remote use make it your docker's default in one command — which is how a laptop borrows a workstation's GPU.
Watch a fleet of them. skrog status --prometheus emits the engine, disk, VM and bridge numbers in node_exporter's textfile format. Local only — you point your own Prometheus at it, nothing phones home.
Install without a network at all. skrog bundle packs the engine into a zip; skrog install --offline consumes it on a machine that has never seen the internet.
And the boring one that people notice first: the Windows-side footprint is about 15 MB of Go binaries, not a desktop application. There is no GUI to keep running, because there is no GUI.
Docker Desktop is a moving target and I have not audited every release of it, so read that list as "things I built because I wanted them and could not get them" rather than a certified feature matrix.
Where the alternatives win
Every project like this posts a comparison table where it wins every row. Here is a more useful one.
| Take it instead of Skrog when | |
|---|---|
| Docker Desktop | You want a GUI, Windows containers, bundled Kubernetes, macOS, or a vendor with a support contract |
| Rancher Desktop | You want a cluster or a graphical UI in the box, or you need macOS/Linux too. It is free, open source, and runs a real engine |
| Podman Desktop | Rootless-by-default matters to you, and you can live with a Docker-compatible API rather than Docker's |
wslc |
You want a first-party runtime with Microsoft behind it, and you genuinely only need build and run
|
Skrog is Windows-only, has no GUI, and will never have Kubernetes. Those are not gaps to fill later; they are the scope. Because it serves the real Docker API, the frontends already exist — Portainer, lazydocker and VS Code all work against it, and kind and k3d run on it, without this project owning any of that.
Worth noting that the engine-pinning above is not just a Desktop gap — none of them do it. If you have ever had "works locally, fails in CI" turn out to be engine drift, that is the row that should decide this for you.
One more thing I only found by testing
Cross-architecture builds are broken on a stock WSL2 engine, mine included. It registers no qemu handlers, so:
$ docker buildx build --platform linux/arm64 .
#5 [2/2] RUN uname -m
#5 0.224 exec /bin/sh: exec format error
Docker Desktop installs binfmt for you, which is why you have probably never met this. It is one privileged container to fix, and the interesting part is that binfmt_misc is kernel state in the WSL2 utility VM, so it evaporates on wsl --shutdown. That makes it a supervisor's job, not a documentation footnote. It is issue #384 and it is next.
Should you use this yet?
Plainly: it depends on how much a warning dialog annoys you.
It is a daily driver. The engine starts at logon, heals itself, survives wsl --shutdown and sleep, and answers docker at the same speed as Desktop. Compose, Testcontainers and Dev Containers all work — Dev Containers with no shim at all, because the bind-mount translation happens in the bridge.
It is also one maintainer, and not code-signed. SmartScreen will warn on first run.
What every release does carry is SLSA build provenance and a cosign-signed SHA256SUMS, which tie the artifact to a workflow and a commit — two things an Authenticode signature does not give you. But SmartScreen does not read either of those, and a scary dialog is a scary dialog.
If an unsigned binary from a young project is a hard no at your company, that is a reasonable position and you should wait. If you are one of the people who got the "stop using Docker Desktop" email and has been running docker through a homemade script ever since, this is that script, finished.
irm https://wslkit.github.io/skrog/install.ps1 | iex
Apache-2.0, no telemetry, nothing auto-updates. github.com/wslkit/skrog
Two things in flight, and one of them you can help with
A winget package is in the approval queue. When it lands, the install is winget install skrog and the PowerShell one-liner above becomes the fallback rather than the front door. Nothing about it is blocked on signing — winget's portable type unpacks per-user — so this is just queue time.
Code signing is blocked on money, and that is the honest version. The SignPath Foundation runs a free programme for open source projects; they declined this one for now, on the reasonable grounds that it has no established user base yet, and invited a reapplication as visibility grows. The other route is simply buying a certificate, which needs nobody's approval and costs a few hundred dollars a year.
That certificate is the single thing standing between a new user and a clean first run. It also unblocks an MSI, because an unsigned installer asking for elevation is worse than a zip, and it starts SmartScreen reputation accruing — which only begins once something is signed at all.
So if this saves you a Docker Desktop seat, GitHub Sponsors is where that goes. I would rather say that plainly than bury it in a FUNDING file: it is not for my time, it is for a certificate that removes a warning dialog for everyone who installs this after you.
And if you would rather contribute in the other currency — bug reports are genuinely more useful to me than money right now. skrog doctor --report produces markdown you can paste straight into an issue. The corporate-VPN cases especially, GlobalProtect and Zscaler and AnyConnect, are the ones I cannot reproduce alone on one development machine.
Top comments (0)