A Windows developer's first day ends with WslRegisterDistribution failed with error: 0x80370102 because virtualisation was disabled, or with a working Ubuntu where docker says The command 'docker' could not be found in this WSL 2 distro because Docker Desktop's WSL integration was never enabled for it, and a repository cloned under /mnt/c/Users/... where npm install takes twelve minutes. Each step is documented somewhere; the sequence, with its reboot in the middle, is not. This page turns it into a resumable PowerShell bootstrap followed by the team's Linux setup inside WSL, as part of workstation provisioning and team dotfiles.

The target: after one script and one reboot, the developer has a WSL2 distribution with systemd, sensible memory limits, Docker available inside it, team dotfiles applied and repositories in the Linux filesystem.

Diagnostic

Run these in PowerShell to see where an existing machine stands:

$ErrorActionPreference = 'Continue'
wsl --status
wsl -l -v
Get-CimInstance Win32_Processor | Select-Object -ExpandProperty VirtualizationFirmwareEnabled
Test-Path "$env:USERPROFILE\.wslconfig"
wsl -d Ubuntu-24.04 -- bash -lc 'ps -p 1 -o comm=; command -v docker || echo "no docker"; pwd'

Expected bad output on a machine set up by hand:

Default Version: 2
  NAME            STATE           VERSION
* Ubuntu-24.04    Running         2
True
False
init
no docker
/mnt/c/Users/ana

WSL2 works, but there is no .wslconfig (so WSL can take half of RAM), PID 1 is init rather than systemd, Docker is not available in the distribution, and the default working directory is on the Windows drive.

WSL2 Settings That Matter for Development Table of WSL2 settings, their default values and the recommended values for development. WSL2 Settings That Matter for Development Setting Default Recommended memory limit 50% of RAM fixed, e.g. 8GB systemd off on older installs on Docker in distro not enabled integration on repo location /mnt/c ~/src in Linux
Defaults work for trying WSL; development stacks need these four settings changed.

Root cause

WSL2 setup spans two operating systems and a reboot. Windows needs the Virtual Machine Platform and WSL features enabled, which requires hardware virtualisation in firmware and a restart before a distribution can be registered. The distribution then needs its own configuration: /etc/wsl.conf to enable systemd (needed by Docker Engine, snap and many services), and the user's .wslconfig on the Windows side to cap memory, because WSL2's default lets it take half of RAM and hold file cache long after containers stop. Docker Desktop's integration is enabled per distribution, and cloning into /mnt/c puts every file access through the 9P bridge between Windows and Linux, which is dramatically slower than the distribution's ext4 filesystem. Manual instructions tend to cover these in the order someone discovered them, not the order that works.

Resolution

  1. Write a resumable PowerShell bootstrap that records progress so it can continue after the reboot:
$ErrorActionPreference = 'Stop'
$state = "$env:LOCALAPPDATA\acme-bootstrap.phase"
$phase = if (Test-Path $state) { Get-Content $state } else { '1' }

if ($phase -eq '1') {
  wsl --install --no-distribution
  Set-Content $state '2'
  Write-Host 'Reboot now, then run this script again.'
  exit 0
}

if ($phase -eq '2') {
  @"
[wsl2]
memory=8GB
processors=6
swap=2GB
[experimental]
autoMemoryReclaim=gradual
"@ | Set-Content "$env:USERPROFILE\.wslconfig"
  wsl --install -d Ubuntu-24.04 --no-launch
  winget install --id Docker.DockerDesktop --exact --accept-package-agreements --accept-source-agreements
  winget install --id Microsoft.VisualStudioCode --exact --accept-package-agreements --accept-source-agreements
  Set-Content $state '3'
}

if ($phase -eq '3') {
  wsl -d Ubuntu-24.04 -u root -- bash -c "printf '[boot]\nsystemd=true\n[user]\ndefault=$env:USERNAME\n' > /etc/wsl.conf"
  wsl --shutdown
  wsl -d Ubuntu-24.04 -- bash -lc 'curl -fsSL https://raw.githubusercontent.com/acme/dotfiles/main/install-linux.sh | bash'
  Set-Content $state 'done'
  Write-Host 'Bootstrap complete.'
}

--no-launch registers the distribution without opening an interactive first-run prompt; the default user is created by the Linux install script. Docker Desktop's integration for the distribution is enabled in its settings (Resources → WSL integration), or by setting integratedWslDistros in its settings file.

  1. Make the Linux script set up the developer's workspace inside the distribution, including a repository directory in the Linux filesystem:
#!/usr/bin/env bash
set -euo pipefail
sudo apt-get update -qq
sudo apt-get install -y -qq git curl jq build-essential unzip
curl -fsSL https://mise.run | sh
sh -c "$(curl -fsLS get.chezmoi.io)" -- -b "$HOME/.local/bin" init --apply acme/dotfiles
mkdir -p "$HOME/src"
grep -q 'cd ~/src' ~/.bashrc || echo '[ "$PWD" = "$HOME" ] || [ "${PWD#/mnt/c}" != "$PWD" ] && cd ~/src' >> ~/.bashrc
docker version --format 'docker ok: {{.Server.Version}}' || echo "enable WSL integration for this distro in Docker Desktop"
  1. Open repositories through the Linux path. In VS Code, code ~/src/shop from the WSL shell opens a Remote WSL window, which runs extensions in Linux and keeps file access native.
Resumable WSL2 Bootstrap Ordered phases of the bootstrap script with a reboot between enabling features and installing the distribution. Resumable WSL2 Bootstrap 1 — phase 1 enable WSL features 2 — reboot required once 3 — phase 2 wslconfig, distro, apps 4 — phase 3 systemd, dotfiles, tools 5 — clone into ~/src in Linux
A phase file lets the same script continue after the reboot instead of starting over.

Expected output

PS> wsl -d Ubuntu-24.04 -- bash -lc 'ps -p 1 -o comm=; docker version --format "{{.Server.Version}}"; free -g | awk "/Mem/{print \$2\" GB\"}"'
systemd
27.2.0
7 GB
$ cd ~/src/shop && time npm ci
added 1412 packages in 41s

systemd is PID 1, Docker works inside the distribution, WSL is capped at the configured memory, and dependency installs run at Linux speed in the Linux filesystem.

The npm ci timing is the clearest before-and-after number to share with the team: the same install under /mnt/c/Users/.../shop typically takes several times longer, and file-watching dev servers there often miss changes entirely. Moving repositories is the single change that most improves Windows developers' daily experience, which is why the bootstrap makes ~/src the default rather than leaving it as advice.

Prevention

  1. Run the Linux half in CI on an Ubuntu 24.04 runner, since it is the same script, and run the PowerShell half on a Windows VM image whenever it changes.

  2. Warn when a repository is on /mnt/c. Add a check to the project doctor script that fails with a clear message if pwd starts with /mnt/.

  3. Keep .wslconfig in the dotfiles repository as a Windows-side file the bootstrap copies, so changes such as a new memory limit reach everyone.

npm ci Duration by Repository Location Bar chart comparing dependency install time in the Windows filesystem versus the WSL Linux filesystem. npm ci Duration by Repository Location repo under /mnt/c 12 min repo under ~/src 41 s
Same repository and machine; the 9P bridge to /mnt/c dominates the slower result.

Platform caveats

WSL2: wsl --install requires Windows 10 22H2 or Windows 11 and hardware virtualisation enabled in firmware; error 0x80370102 means it is off. Corporate devices may also need the Hyper-V platform allowed by policy.

Docker Desktop alternatives: with Docker Engine installed directly in the distribution (no Docker Desktop), systemd must be enabled for the docker service to start automatically.

VPN clients: some VPNs break WSL2 networking by changing routes; enable networkingMode=mirrored in .wslconfig on Windows 11 23H2 or later, which shares the Windows network stack with WSL.

Apple Silicon (ARM64): not applicable; for Windows on ARM devices, use the ARM64 Ubuntu image and check that Docker images used by the project publish arm64 variants.

Rollback

Unregistering the distribution deletes it and everything inside it, so push work first. Features can be disabled from PowerShell:

$ErrorActionPreference = 'Stop'
wsl --unregister Ubuntu-24.04
Remove-Item "$env:USERPROFILE\.wslconfig" -ErrorAction SilentlyContinue
Remove-Item "$env:LOCALAPPDATA\acme-bootstrap.phase" -ErrorAction SilentlyContinue

Frequently Asked Questions

Why is everything so slow when my repository is on C:?

Files under /mnt/c are accessed through a network-like bridge between Windows and the Linux VM. Keep repositories in the Linux filesystem (for example ~/src), and open them in VS Code through Remote WSL.

Do I need systemd in WSL?

For most development stacks, yes. Docker Engine, some language servers and tools that install as services expect systemd. Enable it with [boot] systemd=true in /etc/wsl.conf and run wsl --shutdown.

How much memory should WSL get?

Enough for the development stack plus builds — often 8 GB on a 16 GB machine or 12–16 GB on a 32 GB machine. Set it explicitly in .wslconfig; the default can starve Windows applications.

Can the whole setup run without a reboot?

Not on a machine that has never had WSL enabled; enabling the virtualisation features requires a restart. A phase file lets the script resume cleanly afterwards.