Install on Windows
You work inside the distro and look at the result in a Windows browser. One command starts it. Windows keeps only what the distro cannot own — the certificate authority its browsers trust — and everything else lives on the Linux side.
grim runs inside a WSL2 distro. That is not a variant of installing on Windows, it is how it is installed on Windows: Docker reaches files on the Windows disk through a proxy that makes a Laravel project unusable, and inside the distro it does not.
Before you start
Know which side does what. Windows holds the browser and the certificate authority that browser trusts. The distro holds grim, your projects and every command you type after today. Docker Desktop serves both.
Inside the distro, Docker has to answer, which means WSL integration is switched on for this distro in Docker Desktop. The installer brings grim's own PHP (one self-contained PHP 8.5 binary in ~/.grim/runtime, not on PATH), along with Node and mkcert; the distro needs no PHP and no Composer.
$ docker info --format '{{.ServerVersion}}'The ritual, in order
Run the installer in PowerShell
In PowerShell, not in the distro. The installer first shows a checklist of the parts you can leave out, MariaDB, Node.js + pnpm and mkcert, all ticked (arrows move, space unticks, Enter goes on). The answer travels with the install into the distro, which does not ask again. Under
irm | iexthere is no way to pass options, so the environment answers instead:$env:GRIM_SKIP_MARIADB = '1', and likewiseGRIM_SKIP_NODEandGRIM_SKIP_MKCERT. Any of them set skips the checklist.GRIM_TARGETanswers the other question, Windows or WSL2, in advance, so the run is unattended until the distro asks for something.$ $env:GRIM_TARGET = 'wsl' $ irm https://grim.grimoiry.com/install.ps1 | iexThe installer then does two things and stops touching Windows.
It installs mkcert and puts its authority in the Windows trust store. This step cannot move to the distro: certificates are minted inside WSL2, but whether Chrome, Edge and Firefox warn about them is decided by the Windows trust store alone.
Then it hands over to the Linux installer inside the distro, which notices WSL2 and borrows that authority instead of making one of its own.
Sharing mkcert CA with Windows host: /mnt/c/Users/anna/AppData/Local/mkcert … Found GitHub token from a Windows-side grim install at: /mnt/c/Users/anna/.grim/config.json Reuse it? [Y/n]Sharing mkcert CA with Windows hostis the line this recipe exists for. With no token on the Windows side the installer asks for a classic GitHub token instead; paste it and it is stored in the distro.Two prompts appear along the way and both wait for you: a
sudopassword, when the authority goes into the distro's trust store, and the token question above. The run ends by callinggrim auth:login, which logs Docker into ghcr.io and removes thecredsStoreentry that breaks image pulls inside WSL2. It also hands the token to git, so cloning a private repository needs nothing further.When there is no distro yet
wsl -l -qlisting onlydocker-desktopanddocker-desktop-datameans there is nothing to install into — those two belong to Docker Desktop and are recreated when it resets. The installer says so and installs Ubuntu for you. Ubuntu opens its own window and asks for a UNIX username and password; finish that, then run the installer again and choose1.Open the distro
$ wsl -d Ubuntu $ echo $CAROOT /mnt/c/Users/anna/AppData/Local/mkcert $ grim auth:whoamiAn empty
CAROOTin a fresh shell means your shell reads none of~/.profile,~/.bashrcand~/.zshrc. Copy the marked lines from one of them into the profile it does read.Stop WSL2 from rewriting the hosts file
grim adds each project's names to the distro's
/etc/hosts. WSL2 generates that file again at every restart and the entries are gone. Switch the generation off once per distro, then restart WSL.$ printf '[network]\ngenerateHosts = false\n' | sudo tee -a /etc/wsl.conf $ wsl.exe --shutdownThe shutdown closes every distro and this terminal with them. When
/etc/wsl.confalready has a[network]section, put the line there by hand instead. Open the distro again before the next step, and wait until Docker Desktop reports the engine running.Install the database
The Linux installer does not install a database server. It prints the packages and one
GRANTstatement and leaves them to you. grim creates each project's database asrootwithout a password, so do this before the first project.$ sudo apt install mariadb-server && sudo systemctl start mariadb $ sudo mysql -u root -e "CREATE USER IF NOT EXISTS 'root'@'%' IDENTIFIED BY ''; GRANT ALL ON *.* TO 'root'@'%' WITH GRANT OPTION; FLUSH PRIVILEGES;"Ask the doctor
$ grim doctor … Docker daemon ✓ running MySQL/MariaDB ✓ 10.11.6 (root@127.0.0.1, no password) … WSL2 browser path mkcert CAROOT ✓ /mnt/c/Users/anna/AppData/Local/mkcert Windows CA ✓ /mnt/c/Users/anna/AppData/Local/mkcert/rootCA.pem certutil ✓ /usr/bin/certutil Browser ✓ Snap Firefox not on PATH (use Windows browser for *.test) All checks passed!A
-on theMySQL/MariaDBline does not fail the run, and it still matters: with a-there, the first project gets no database.grim setup— the shared Docker network, the authority in the distro's trust store, the single Traefik every project routes through — is not a step here.grim updoes all of it on first use.
What you have now
| Where | What |
|---|---|
| Windows | The mkcert authority in the trust store, and nothing else to maintain |
| Distro | ~/.local/bin/grim, ~/.grim/config.json, ~/.grim/traefik/, CAROOT and a git credential for github.com |
| Docker Desktop | The traefik-public network and one Traefik on ports 80 and 443, its dashboard on 127.0.0.1:8099 |
Keep projects under ~, never under /mnt/c. Both paths are reachable from the distro and only one of them works: /mnt/c is the Windows disk, which Docker reaches through a proxy that charges for every stat(). On one Laravel codebase, the same commands from each:
Project on /mnt/c |
Project under ~ |
|
|---|---|---|
php artisan --version |
34–48 s | 1.2 s |
du -sh vendor |
148 s | 0.9 s |
| First byte of a request | 10–27 s | 0.15 s |
Windows can still open those files when you need them — \\wsl$\Ubuntu\home\you\ in Explorer, and editors with a WSL mode work on them directly.
No project yet. That is the next recipe, Create a project. Its one Windows-specific moment is a UAC prompt on the desktop, when the project's names go into the Windows hosts file. If you miss it, grim hosts:windows asks again.
When it does not work
“Failed to download the PHP runtime”
The installer could not download grim's PHP (no network to dl.static-php.dev). Run it again once the distro reaches the internet. does not match its pin instead means the file that came down is not the one grim's release pinned; nothing was installed, and the macOS recipe says what to do.
“The Linux installer reported success but grim is not on PATH”
It stopped partway. The distro's log holds the last step it reached, and the installer leaves itself in /tmp, so a second attempt needs no download and shows everything on screen.
$ wsl -d Ubuntu -- bash -lc "tail -40 ~/.grim/install.log"
$ wsl -d Ubuntu -- bash /tmp/grim-install.sh“No Windows mkcert CA found at /mnt/c/Users/*/AppData/Local/mkcert/.”
The distro made an authority of its own, and Windows browsers reject every certificate signed by it. Run mkcert -install in PowerShell, then run the distro installer again. Doctor shows the same state as Windows CA ✗.
A clone asks for a password and then refuses it
GitHub stopped accepting account passwords for git in 2021, so the account password can never work here. The installer writes the token into ~/.git-credentials to avoid this; when the prompt appears anyway, that file has no github.com line. Before touching git, check that the token reaches the repository at all — 404 is an access problem on the account, not a git one.
$ curl -s -o /dev/null -w "%{http_code}\n" -H "Authorization: Bearer $TOKEN" https://api.github.com/repos/ORG/REPOWSL2 cannot be used on this machine
Some machines cannot run it: virtualization switched off in firmware or by policy, a VDI without nested virtualization, an account that may not install a distro. Setting GRIM_TARGET to windows installs everything on the Windows side instead, and the installer provisions grim's own PHP, MariaDB, Node and mkcert there. Expect the numbers in the table above, the wrong way round, on every project you keep on C:.
grim is not found in PowerShell
Nothing is wrong. In this setup grim lives in the distro, and the projects it works on are paths inside the distro. Open the distro and work there.
“error getting credentials” when an image is pulled
The distro's ~/.docker/config.json names "credsStore": "desktop" again, and that helper fails inside WSL2. Run grim auth:login again. It removes the entry and logs in without it.
Firefox in the distro still warns about the certificate
Snap Firefox detected on PATH in doctor explains it. A Snap browser keeps a sandboxed trust store the authority cannot be installed into. Use the Windows browser for .test pages.
Next recipe
Run grim projects beside Laravel Herd
Keep Herd's sites and grim's Docker stacks on one Mac at the same time, with Herd's nginx in front of Traefik and every project answering at its own .test name.
Read itSpells used here
grim setup
Prepare the machine
Put the shared pieces of local development in place, once per machine: the Traefik proxy, a trusted local certificate authority and .test name resolution.
grim doctor
Check your circle
Check every tool grim expects on this machine, and the project you are standing in, and say what to do about what is missing.
grim auth:login
Log grim in to GitHub
Give grim a GitHub token, from your gh login or a personal access token, and hand the same token to Composer and Docker.
grim auth:whoami
See who grim acts as
Show the GitHub user and the masked token grim holds on this machine.
grim hosts:windows
Reach WSL2 from Windows
Write the project's .test domain into the Windows hosts file from inside WSL2, so a Windows browser can open it.