skrog (n., Norwegian) — the hull: the body of the ship that carries the cargo and keeps the sea out.
A minimal, invisible way to run the upstream open source Docker Engine on Windows via WSL2.
No license fees, no Electron, no Kubernetes — install once, docker ps works forever, on
laptops and CI runners alike.
It is a free, open source alternative to Docker Desktop — the same upstream dockerd
underneath, without the subscription larger companies now need, the desktop application, or
the auto-update that changes your engine mid-sprint. Rancher Desktop and Podman
Desktop are free too; Skrog differs by being headless, and by running dockerd itself
rather than a Docker-compatible API. How it compares is the honest
version, including where each of them wins.
Status: pre-release. Installable and working as a daily driver: install once and
the engine starts at every logon, heals itself, and answers docker from a resident
bridge whose own cost is tens of milliseconds
(measured; most of what you wait for is
docker.exe starting up, which every Windows Docker CLI pays).
What changed in each release is CHANGELOG.md; read
PLAN.md for the strategy and ROADMAP.md for the
schedule, and the issue tracker is
the live state. Security reports go through
SECURITY.md.
One maintainer, young, and not yet code-signed — so SmartScreen warns on first run. If that matters more to you than the licence does, come back in a few months.
Requirements: WSL 2.x from the Store or MSI on a virtualization-capable machine. Windows 11 is the primary target; Windows 10 22H2 (build 19045) is tested and works.
docs/install.md is the step-by-step guide — what to download, how to verify it, and what to run. The short version is below.
You also need a docker command, and Skrog does not install one by default: Docker
Desktop's works (Skrog coexists with it), and on a machine without it skrog cli install
fetches the upstream tools — see docs/docker-cli.md.
irm https://wslkit.github.io/skrog/install.ps1 | iexOr with scoop:
scoop bucket add skrog https://github.com/wslkit/scoop-skrog
scoop install skrogThe one-liner downloads the newest release, verifies it against the release's SHA256SUMS,
unpacks it to %LOCALAPPDATA%\Programs\skrog and adds it to your PATH. It stops there
on purpose — it does not provision anything. Then:
skrog install— downloads the checksum-verified engine rootfs, imports it as theskrog-engineWSL2 distro, starts the engine, wires askrogdocker context, and registers the supervisor to start at logon (--no-autostartopts out)skrog start— brings up the always-on bridge now (from your next logon it starts itself)docker run --rm hello-world
If Docker Desktop is not running, Skrog serves \\.\pipe\docker_engine — the pipe
docker already talks to — so nothing else is needed. The skrog docker context
install wires up is for the other case: when Desktop owns that pipe, Skrog serves its
own, and docker --context skrog ... (or docker context use skrog) is how you reach
it. The install output names the pipe it took, and docker context inspect skrog shows
it at any time.
Binaries are not Authenticode-signed, so SmartScreen warns on first run. The
SignPath Foundation's free programme declined for now — it is
for projects with an established user base — and invited a reapplication as visibility
grows; paying for a certificate is the other route and needs nobody's approval. Which
one, and when, is #77. An MSI waits on the
same answer, since an unsigned installer asking for elevation is worse than a zip; a
winget package does not, and one is on the way. The
code signing policy says who could produce
a signed binary and how. What every release does carry today is SLSA build
provenance and a cosign-signed SHA256SUMS — two checks an Authenticode signature does
not give you, since they tie the artifact to a workflow and a commit. See
verifying a download.
Prefer to do it by hand? Download the zip for your architecture from the
latest release, check it against
SHA256SUMS, and unpack it anywhere on your PATH — the script does nothing else.
Read it first if you would rather not pipe a URL into your shell;
it is the same file that URL serves.
On a CI runner, use setup-skrog instead.
skrog.exe uninstall removes everything Skrog created — the distro and all images and
volumes in it, the autostart entry, any distro integrations — and restores your previous
docker context. Nothing else on the system is touched.
- Upstream Docker Engine (Linux containers) in a dedicated WSL2 distro — the real API, byte
for byte: compose, buildx, Testcontainers,
run -it, bind mounts with Windows paths - Dev Containers, with no shim:
devcontainer upand VS Code's "Reopen in Container" build and run against Skrog unchanged — theC:\…\project→/workspaces/…bind mount is translated by the bridge, and Features,postCreateCommandand docker-outside-of-docker all behave as they do on any Linux engine (docs/devcontainers.md) - Always-on supervisor: starts at logon, survives engine crashes,
wsl --shutdown, and sleep/resume;skrog start/stop/restart/status --json. Settings apply live — the supervisor follows the file, so nothing here needs a restart;skrog restart --supervisorreplaces the supervisor process itself on the rare occasion that helps - Low-overhead transport: a vsock path to the engine, with an automatic fallback.
Measured on the reference host (Win10 22H2, n=10,
#326):
docker version226 ms,docker ps209 ms. Most of that isdocker.exestarting — the client-only spawn floor on the same machine is ~165 ms — so the bridge costs tens of milliseconds, not hundreds. Docker Desktop has not been benchmarked on this host, so there is no parity claim; this line used to assert "~80 ms, at parity with Desktop" and neither half had a measurement behind it - Idle RAM answer:
skrog config set idle-timeout 30mstops a quiet engine and wakes it on your nextdockercommand. The wake is not yet measured (#398) — what is measured is a fullskrog stopthenstartto a running container, 6.6–9.4 s. Idle-wake should be well under that, because the distro stays registered and only dockerd has to come back; but "should be" is not a number, and the "~1 s" this line used to quote was a guess skrog doctor: diagnoses the WSL / PATH / credential-helper / ssh-agent / supervisor quirk zoo, with--json,--report(paste straight into an issue), and--fixfor the safe subset; recognizes corporate VPNs (GlobalProtect, AnyConnect, Zscaler…) and prints the MTU/DNS fix (docs/vpn.md)- Validated engine settings:
skrog config set engine.<key>edits the engine'sdaemon.json(registry mirrors, logging, DNS…), checked withdockerd --validatebefore it applies and rolled back if the engine will not come back - Lifecycle hooks: run your own script on post-start / pre-stop / on-idle-stop / on-wake (docs/hooks.md)
- Declarative installs:
skrog install --config skrog.yaml(idempotent) andskrog config export— infrastructure-as-code for a fleet (docs/declarative-install.md) - NVIDIA GPU:
skrog enable-gpu, thendocker run --device nvidia.com/gpu=all …runs CUDA workloads (Ollama, vLLM, PyTorch) — a hookless CDI spec that works on the musl engine, no toolkit installed (docs/gpu.md) - Bundled docker CLI:
skrog cli installinstalls the upstream docker CLI + compose + buildx + credential helper — checksum-pinned, nothing fetched as "latest" — so you can uninstall Docker Desktop entirely (docs/docker-cli.md) - Remote engine over mutual TLS:
skrog serve --tcpexposes the engine to a teammate or CI runner, reachable only by holders of a client cert this machine's CA signed — off by default; on the client,skrog remote add/usemakes it docker's default in one command (docs/remote-engine.md) skrog status --stats: container/image/volume counts and reclaimable space, the VHDX footprint, VM memory and CPUs (configured versus actual), engine and supervisor uptime with idle-stop history, and bridge counters including which transport is live — the one number that explains a slowdockerwith a healthy engine.--prometheusemits the same numbers for node_exporter's textfile collector, so a fleet's health lands in the dashboard you already run — local only, still no telemetry (docs/monitoring.md)- Right-size the VM with consent:
skrog config set wsl.memory 4GBthenskrog wsl-config applyshows the diff to the GLOBAL ~/.wslconfig and writes only on a yes (--yesfor runners, idempotent) — docs/vm-sizing.md - Engine upgrades, reversibly:
skrog engine upgradeswaps the engine binaries out of a checksum-verified rootfs and leaves /var/lib/docker alone, so images and volumes survive; a new engine that does not come back is rolled back automatically (docs/engine-upgrade.md) - Disk hygiene:
skrog prunereclaims stopped containers, unused images and build cache through whatever docker targets;skrog compactthen shrinks the engine's VHDX itself (fstrim+CompactVirtualDisk, no administrator rights, so it works on Windows Home);skrog doctorwarns below a configurable free-space floor.skrog config set prune.every 168hhands the job to the supervisor — opt-in, age-guarded, and never volumes (docs/housekeeping.md) - Pull-through registry cache:
skrog cache enableruns the upstreamregistry:2on the engine so a layer is fetched from the internet once — the answer to Docker Hub's per-IP rate limit behind a corporate NAT or across a runner fleet. Loopback only, pinned by digest, and excluded from the idle-stop probe so it never holds the engine awake (docs/registry-cache.md) - Admission control: a small, fixed rule set — refuse
--privileged, restrict bind sources, require digests, allow only listed registries — judged at the pipe on create, pull and push, before the engine sees the request. A machine-wide layer under%ProgramData%merges with the user's, and the user layer can only tighten it, so a fleet can deploy rules by Intune or GPO. It is configuration management, not access control, and docs/policy.md is precise about the difference - Shell completions: PowerShell and bash, generated from the binary and drift-checked in CI, so they cannot describe flags the build does not have (docs/install.md)
skrog wsl-integrate <distro>: use the engine from inside your own WSL distrosskrog migrate: copy images and volumes out of Docker Desktop, Rancher Desktop (--from-rancher) or Podman (--from-podman) — non-destructively and resumably (--dry-runfirst), so trying Skrog never means starting from an empty engine- Optional status-light tray (
skrogtray.exe) — seven menu items: start/stop/restart, open logs, run doctor, check for updates, quit. It never grows into a container manager - Headless CI installs (
--headless, exit codes,--jsonon every state-reporting command — the contract in docs/cli-json.md),skrog healthcheck --waitas a runner readiness probe,skrog logs --jsonfor log shippers,skrog prewarm images.txtto pre-pull a pinned image list, version pinning as a contract (nothing fetches "latest"), no telemetry - A logged-on session is required — a WSL2 platform constraint that binds every WSL-based
engine; for CI runners see docs/auto-logon-runner.md, and
skrog runner checkverifies the setup in one verdict - The supervisor is watched: if it ever dies,
skrogw.exerestarts it in about a second (backing off, with a crash budget) instead of leaving everydockercommand broken until the nextskrog start - CI runners: GitHub Actions via setup-skrog, GitLab shell or docker executor, Testcontainers (Ryuk included) — docs/ci-runners.md
- Debug pipelines locally, against the engine your runner uses:
act,gitlab-ci-local, Dagger, and a BuildKit cache a laptop and a runner share; commitskrog.lockand both install the same engine to the commit — docs/local-ci.md - Kubernetes when you want it, never bundled:
kindandk3dclusters run on the engine, withkubectland NodePort/Ingress reachable from Windows — docs/kubernetes.md
A winget package, and signed installers beyond it — tracked in the
issue tracker. Data-dir relocation
shipped: see skrog relocate.
The honest version, including where the alternatives win.
Docker Desktop is why most people arrive: since 2021 it needs a paid subscription for larger companies, and a lot of developers are told to stop using it. Skrog is the engine underneath it — the same upstream dockerd — without the desktop application, the licence, or the auto-update that changes your engine mid-sprint. Desktop gives you a GUI, Windows containers, Kubernetes and macOS support; Skrog gives you none of those, deliberately.
Rancher Desktop also runs a real container engine on WSL2, free and open source, with a
GUI and k3s in the box. If you want a cluster or a graphical UI, take Rancher — Skrog has
neither and will not grow them. It is also cross-platform, where Skrog is Windows-only.
If you have already built up images there, skrog migrate --from-rancher copies them over
without touching Rancher.
Podman Desktop fronts podman, whose API is Docker-compatible rather than Docker. That
distinction is usually invisible and occasionally expensive: rootless defaults, compose
handled by a shim, and the corners where tooling reaches for dockerd's actual behaviour.
Skrog runs dockerd itself, so there is no compatibility surface to fall off. That same
compatible API is how skrog migrate --from-podman reads your images out.
wslc is Microsoft's own, and if you have updated WSL recently you already have it —
wslc.exe ships with WSL 2.9.3+ and is headed for general availability. It runs, builds
and networks Linux containers, with GPU support, and there is a Microsoft.WSL.Containers
NuGet for driving containers from a Windows app. It is the strongest argument against
needing this project at all, and if you want a first-party runtime with Microsoft behind
it, use it.
What it does not give you is an endpoint. Every wslc session really does run a Moby
engine — dockerd on a unix socket inside the session VM — but it is started with no -H,
and no named pipe, TCP port or inbound route reaches it from Windows. So nothing that
already speaks Docker can talk to it: Compose, Testcontainers, Dev Containers, buildx,
act, gitlab-ci-local, Dagger. Skrog serves that endpoint — the real docker API on
\\.\pipe\docker_engine — which is why that list works here without any of those tools
knowing Skrog exists. wslc also
cannot bind-mount WSL host paths yet,
which rules out docker-in-docker — a limitation of its CLI rather than of the engine
underneath. wslc and Skrog has the full picture: what is
actually inside a session, why docker cannot reach it, and how you can check both
yourself in one command.
Skrog can also serve that engine: skrog install --engine wslc makes a wslc
session this machine's engine, and skrog start, status, version and a
doctor check all know it. skrog proxy --engine wslc runs the same bridge in
the foreground if you would rather just try it. Compose, Testcontainers and
Windows-folder bind mounts all work. It is experimental, it cannot pin an
engine, and a session costs a second VM. wslc as the engine
is the full guide, with the measured numbers and a pros-and-cons table.
What none of them do is let you pin the engine. skrog lock writes the exact dockerd,
containerd, runc and BuildKit commits; setup-skrog installs that same file on the runner.
Docker Desktop ships whatever version it ships, and updates it on its own schedule.
Reasons to pick something else, stated plainly: you need a GUI, Windows containers, macOS or Linux, a bundled Kubernetes, a vendor with a support contract, or a runtime that ships with the OS and you do not need the Docker API. Skrog is one maintainer and a young project.
Windows containers, Kubernetes, or a management GUI. Because Skrog serves the standard
Docker API, existing frontends (Portainer, lazydocker, VS Code) already work against it —
and a cluster is just containers, so kind and k3d run on it today
(docs/kubernetes.md) without Skrog owning a control plane.
Standard Go project layout — the Go toolchain, not a framework, decides this shape:
cmd/skrog/ the CLI — the product; every capability lives here
cmd/skrogw/ windowless logon launcher and supervisor watchdog (no console flash)
cmd/skrogtray/ optional status-light tray; shells out to the CLI, holds no logic
internal/ implementation packages, compiler-enforced private to this module
wsl/ every wsl.exe call, behind an interface so tests run anywhere
... provision, pipeproxy, supervise, config, migrate, integrate,
doctor, engineconfig, skrogfile, tray
guest/ Linux side: rootfs build scripts, vsock agent
docs/ operator docs, e.g. the unattended/auto-logon runner playbook
site/ the docs site: layouts and nav only -- content comes from docs/
test/e2e/ cross-package suite; the only part needing real WSL2
spike/ throwaway experiments, deleted once their issue closes
Running unattended (CI runners, build agents) needs a logged-on session, because
WSL2 cannot start from a Windows service — see
docs/auto-logon-runner.md. Who can reach the engine
and where the trust boundaries lie (the pipe ACL, wsl-integrate's shared
socket, the supply chain) is documented in docs/security.md.
Two Go conventions worth stating, since they surprise people arriving from other ecosystems:
tests live beside the code they test (wsl.go and wsl_test.go in the same folder — the
_test.go suffix is how the toolchain finds them, and package-private tests need it), and
there is no src/ — the module root is the source root, and internal/ is a compiler
rule (nothing outside this module can import it), not a naming preference. test/e2e/ exists
only for suites that belong to no single package.
Also published as a site: https://wslkit.github.io/skrog/, generated from these same files. Grouped below by the question you arrived with; every page is reachable from here.
Start Installing Skrog · Bundled docker CLI · Dev Containers · wslc and Skrog · wslc as the engine
Keep it healthy Housekeeping: prune, compact, relocate · Snapshots · Staying current · Engine upgrade and rollback · VM sizing
At work Corporate networks · VPNs · Air-gapped installs · Registry cache · Security and trust boundaries · Code signing policy · Audit log · Admission control
Fleet and CI CI runners · Unattended / auto-logon runners · Declarative install · Profiles · Remote engine over mTLS · Local CI · Monitoring a fleet
Advanced GPU (NVIDIA, and experimental AMD) · Kubernetes · Lifecycle hooks · JSON output contract · Command reference
Project Changelog · Reporting a vulnerability · Releasing · Bumping the engine and docker CLI · Design notes · Per-workspace engines
Editing the docs edits the site: pwsh -File scripts/build-docs.ps1 -Serve
previews it locally, and merging to main publishes it. Nothing in site/ is
hand-written prose except the front page.
Apache-2.0. Docker and the Docker logo are trademarks of Docker, Inc. Skrog is not affiliated with or endorsed by Docker, Inc.