kai (n., Norwegian) — the quay: the edge of the dock where a ship is tied up and worked. Sibling to skrog, which is Norwegian for the hull.
A small aid for people using wslc who have Docker muscle memory. It takes
a Docker command line, runs the wslc equivalent, and stops with a reason
where wslc has no equivalent.
kai ps
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
9a04f5414434 busybox "sleep infinity" 2 days ago Up 2 days web
That is wslc list, and the output is wslc's — kai says nothing of its own
when a translation is clean. When it will not run something, it says why:
kai run --restart unless-stopped nginx
kai: cannot run 'docker run --restart unless-stopped nginx'.
wslc has no restart policy. A container that exits stays exited. Use
a real Docker engine: Docker Desktop, Rancher Desktop, or skrog
(https://github.com/wslkit/skrog).
--dry-run shows the translation without running it:
kai --dry-run run -d -p 8080:80 --name web nginx
docker docker run -d -p 8080:80 --name web nginx
wslc wslc run -d -p 8080:80 --name web nginx
verdict identical, no rewrite needed
kai --dry-run run --restart unless-stopped nginx
docker docker run --restart unless-stopped nginx
wslc (cannot be expressed)
error wslc has no restart policy. A container that exits stays
exited. Use a real Docker engine: Docker Desktop, Rancher
Desktop, or skrog (https://github.com/wslkit/skrog).
This matters more than the list of features, because every failure mode of a tool like this comes from quietly becoming one of these.
- Not a Docker-compatible CLI. No API, no daemon, no compatibility promise.
- Not an emulation layer. It rewrites spelling and reports the result. It
never approximates: it will not implement a missing capability, fake a
semantic
wslclacks, or drop an argument it cannot express. A single unsupported flag fails the whole invocation rather than running the rest. - Not a compose runner. kai never executes
compose. It will read a compose file and tell you whatwslccould not honour — see below. - Not a competitor to skrog. skrog
serves the real Docker API, so unmodified
docker, Compose, Testcontainers, buildx and Dev Containers work. kai serves a person typing at a prompt. Different problem, different answer. - Not a product with a support promise. A personal aid that happens to be public.
Pre-release, v0.2: kai executes. It translates and runs, and refuses
outright where wslc has no equivalent. What it will never do is approximate —
a single unsupported flag fails the whole invocation rather than running the
rest, because a container that came up without the flag you asked for looks
like it worked.
Three rules it holds to while running things:
-
stdout belongs to wslc. Every message kai emits goes to stderr, so
kai ps | ConvertFrom-Csvsees exactly whatwslc listwould have produced. -
the exit code is wslc's, unchanged. kai's own refusal uses 125, which is distinguishable —
wslcreturns 1 for its own failures. -
interactive works, because kai never captures output.
kai run -itandkai exec -itinherit kai's own stdin, stdout and stderr.One limit worth knowing, measured rather than assumed: kai is a PowerShell function, so piping into it from PowerShell does not reach the container —
'x' | kai run -i --rm busybox catgets you nothing. Pipeline input binds to the function; the container inherits the process's stdin. Redirecting a file, typing at a console, or piping fromcmd/bash all work.
Tables measured against WSL 2.9.12, 2026-09-21. The wslc surface moves
every release — re-measure before trusting them.
Aliasing docker to kai is not here yet; that is v0.3, and it is the one
genuinely hazardous feature (§06 rule 1), so it arrives opt-in and refusing to
shadow a real docker.
- PowerShell 7.3 or newer. kai pins
$PSNativeCommandArgumentPassing = 'Standard'inside the module, and that mode does not exist below 7.3. It guards against inheritingLegacyfrom a caller's profile, which corrupts exactly the arguments Docker users pass — a spaced-evalue, a--formattemplate, a JSON--entrypoint. - WSL with
wslc.wslc.exelives inC:\Program Files\WSL. Unlikewsl.exeit has no alias stub inSystem32, so whether a barewslcworks depends on your shell. kai resolves it explicitly. powershell-yaml, and only forkai compose check. Its absence is a supported state, not a broken one: the check says so and everything else keeps working.
kai is not on the PowerShell Gallery yet. Per its own plan, publishing waits until somebody other than the author has run it.
git clone https://github.com/wslkit/kai
Import-Module ./kai/kai.psd1When it is published, the two supported routes will be:
Install-Module kai # or: Install-PSResource kaiIf you install from a downloaded .zip instead, Windows marks the files as
coming from the internet and RemoteSigned will refuse to load them.
Unblock them first:
Unblock-File -Path .\kai\*.ps*1No certificate is needed for any of this. That is deliberate — see PLAN.md §07a.
kai --dry-run cp ./local.txt web:/tmp/local.txt
docker docker cp ./local.txt web:/tmp/local.txt
wslc wslc container cp ./local.txt web:/tmp/local.txt
verdict rewritten -- wslc keeps cp under the container group rather
than at the top level.
Where there is no equivalent at all, kai says what is missing, why it matters, and what to use instead:
kai --dry-run events
docker docker events
wslc (no equivalent)
error wslc exposes no engine event stream. Tools that reap containers
by watching it -- Testcontainers' Ryuk is the one people hit --
cannot work here at all. Use a real Docker engine: Docker
Desktop, Rancher Desktop, or skrog
(https://github.com/wslkit/skrog).
kai exits 125 when it refuses an invocation, which is distinguishable from
anything wslc returns — wslc uses 1 for its own failures. The one
ambiguity, shared with Docker for the same reason: a container that itself
exits 125.
kai never runs compose. It will read one and tell you exactly what would be ignored — which is the more useful thing, because nothing errors when a key is dropped; the stack just comes up wrong.
kai compose check docker-compose.yml
services: 3
api
image supported
ports supported
depends_on NOT SUPPORTED Dependency order is ignored. Services may start in any order.
healthcheck NOT SUPPORTED No health checking, so nothing waits for a service to become ready.
environment supported
db
image supported
volumes supported
environment supported
worker
build NOT SUPPORTED wslc compose cannot build. Pre-build the image and reference it by name instead.
restart NOT SUPPORTED No restart policy. A container that exits stays exited.
verdict: this file cannot run correctly under wslc compose.
4 of 10 keys used are unsupported, and each of them is
ignored rather than rejected -- the stack comes up wrong
with nothing reporting an error.
Use a real Docker engine: Docker Desktop, Rancher Desktop,
or skrog (https://github.com/wslkit/skrog).
This report is provisional and says so in its own output. wslc compose
has not shipped: the supported-key list is read from the C++ struct in an open
draft pull request (microsoft/WSL#41526), not from anything you can run. It
gets re-checked against the header when that lands.
Measured against WSL 2.9.12. Four groups, and most of the surface is in the first one — the gap is much smaller than it looks.
kai passes these straight through and says nothing. There is nothing to translate.
attach |
build |
create |
exec |
export |
images |
import |
info |
inspect |
kill |
load |
login |
logout |
logs |
pull |
push |
restart |
rmi |
run |
save |
start |
stats |
stop |
tag |
version |
And the command groups container, image, network, volume and system.
wslc accepts the Docker spelling directly, so kai stays out of the way — §06 rule 3: never intercept what already works.
| you type | wslc runs | who handles it |
|---|---|---|
docker ps |
wslc list |
wslc, natively |
docker rm |
wslc remove |
wslc, natively |
| you type | kai runs | why |
|---|---|---|
docker cp |
wslc container cp |
wslc keeps cp under the container group rather than at the top level |
That is the whole of it. One command.
kai refuses these outright and says what to use instead. Eighteen of them are
Docker commands with nothing behind them in wslc; the nineteenth is compose,
which wslc has not shipped and kai would never run anyway.
| command | what is missing |
|---|---|
commit |
cannot create an image from a container's current state |
diff |
cannot report filesystem changes inside a container |
events |
no engine event stream — see below |
history |
cannot show an image's layer history |
pause / unpause |
cannot suspend or resume a container's processes |
port |
no command lists a container's published ports |
rename |
cannot rename an existing container |
top |
cannot list processes inside a container |
update |
cannot change a running container's limits |
wait |
cannot block until a container exits |
search |
cannot search a registry |
manifest |
no multi-architecture manifest tooling |
plugin |
no plugin system |
swarm |
no clustering or orchestration |
context |
no notion of a context pointing at another engine |
bake / builder |
Buildx features; wslc's build is not Buildx |
compose |
not shipped, and kai never runs it — use kai compose check |
events is the one that bites hardest: Testcontainers' Ryuk watches the engine
event stream, so a whole class of tooling cannot work here at all.
A command existing is not the same as a flag existing. 20 common
docker run / docker create flags are rejected outright, including
--restart, --privileged, --add-host, --device, --cap-add,
--platform, --volumes-from and --read-only.
kai refuses the whole invocation when one appears, rather than dropping it and running the rest.
You do not need to memorise any of this. Ask kai about the command you were
about to type — kai --dry-run <command> — and it will tell you at the moment
you need to know.
Docker Desktop, Rancher Desktop, or skrog — which serves the real Docker API on Windows via WSL2, so Compose, Dev Containers and Testcontainers work unmodified.
No "docker" and no whale in the repository name, the module name, or any
package id. The mark is not ours to use as a name. Referring to what kai does —
"lets you type Docker commands against wslc" — is fine, and is how this
should be described.
See CONTRIBUTING.md. The design, the scope fence and the measurements behind all of the above are in PLAN.md.
Apache-2.0. See LICENSE.