All commands
Every command and config file, grouped by recipe. For when you know what you want; follow the recipe link for prerequisites and how to check it worked.
Set up a new machineStart here
wsl --install -d Ubuntuwsl --update
wsl -l -v # Ubuntu, VERSION 2sudo apt update && sudo apt full-upgrade -y
sudo apt install -y build-essential git curl unzipmkdir -p ~/dev
cd ~/devcd ~/dev
code .curl https://mise.run | sh
echo 'eval "$(~/.local/bin/mise activate bash)"' >> ~/.bashrc
exec bash
mise use -g node@lts # or python@3.13, go, java… whatever your project needsgit config --global user.name "Your Name"
git config --global user.email "you@example.com"
git config --global init.defaultBranch main
git config --global core.autocrlf inputcd ~/dev
npm create vite@latest hello -- --template vanilla
cd hello && npm install && npm run devOpen a WSL project in your editorDaily development
cd ~/dev/my-app
code . # or: cursor .Install Node, Python and other runtimes with miseDaily development
curl https://mise.run | sh
echo 'eval "$(~/.local/bin/mise activate bash)"' >> ~/.bashrc
exec bashmise use -g node@lts pnpm@latest bun@latest python@3.13 uv@latest
mise settings add idiomatic_version_file_enable_tools node # respect .nvmrc / .node-versionmise use node@22 # pin for THIS project
mise install # install what the project asks for
mise ls # what's installed and active
mise up # upgrade everythingwhich node # ~/.local/share/mise/installs/node/...
node -v
cd ~/dev/my-app && mise current # versions the project pinsmise implode # removes mise and its installsConnect Git to GitHub with SSH and signed commitsDaily development
sudo apt install -y ghssh-keygen -t ed25519 -C "you@example.com" -f ~/.ssh/id_ed25519git config --global gpg.format ssh
git config --global user.signingkey ~/.ssh/id_ed25519.pub
git config --global commit.gpgsign true
git config --global tag.gpgsign true
echo "you@example.com $(cat ~/.ssh/id_ed25519.pub)" > ~/.ssh/allowed_signers
git config --global gpg.ssh.allowedSignersFile ~/.ssh/allowed_signersgh auth login # GitHub.com → SSH → skip key upload
gh auth refresh -s admin:public_key,admin:ssh_signing_key
gh ssh-key add ~/.ssh/id_ed25519.pub --title "$(hostname) WSL" --type authentication
gh ssh-key add ~/.ssh/id_ed25519.pub --title "$(hostname) WSL signing" --type signinggit config --global pull.rebase true
git config --global rebase.autoStash true
git config --global push.autoSetupRemote truessh -T git@github.com # "Hi <user>! You've successfully authenticated"
git log --show-signature -1 # after your next commit: "Good \"git\" signature"Install Docker in WSLDaily development
ps -p 1 -o comm= # prints: systemdcurl -fsSL https://get.docker.com | sh
sudo usermod -aG docker $USER # then close and reopen the terminal{
"log-driver": "local",
"log-opts": { "max-size": "20m", "max-file": "3" },
"features": { "buildkit": true, "containerd-snapshotter": true },
"builder": { "gc": { "enabled": true, "defaultKeepStorage": "30GB" } }
}sudo systemctl restart dockerdocker run --rm hello-world
docker compose version
docker info --format '{{.LoggingDriver}}' # localsudo apt purge -y docker-ce docker-ce-cli containerd.io docker-buildx-plugin docker-compose-plugin
sudo rm -rf /var/lib/docker /var/lib/containerd /etc/docker/daemon.jsonRun shared Postgres and Redis for all projectsDaily development
name: devinfra
services:
postgres:
image: postgres:17-alpine
restart: unless-stopped
environment: { POSTGRES_USER: dev, POSTGRES_PASSWORD: dev, POSTGRES_DB: dev }
ports: ["127.0.0.1:5432:5432"]
volumes: [pgdata:/var/lib/postgresql/data]
redis:
image: redis:7-alpine
restart: unless-stopped
command: ["redis-server", "--appendonly", "yes"]
ports: ["127.0.0.1:6379:6379"]
volumes: [redisdata:/data]
volumes: { pgdata: {}, redisdata: {} }echo 'alias devinfra="docker compose -f ~/dev/infra/docker-compose.yml"' >> ~/.bashrc
source ~/.bashrc
devinfra up -ddevinfra ps # both "running"
devinfra exec postgres pg_isready -U dev # accepting connections
devinfra exec redis redis-cli ping # PONGdevinfra down # keep data
devinfra down -v # delete volumes tooConnect a container to a service in WSLDaily development
services:
worker:
image: my-worker
extra_hosts:
- "host.docker.internal:host-gateway"
environment:
API_URL: http://host.docker.internal:3000docker run --rm --add-host=host.docker.internal:host-gateway curlimages/curl \
-s http://host.docker.internal:3000/healthdocker compose exec worker sh -c 'wget -qO- http://host.docker.internal:3000/health || curl -s http://host.docker.internal:3000/health'Turn on mirrored networkingDaily development
[wsl2]
networkingMode=mirrored # WSL shares Windows' IPs; localhost works both ways
dnsTunneling=true # fixes DNS on VPNs and odd networks
firewall=true # Windows firewall rules apply to WSL
autoProxy=true # use the Windows proxy settings
[experimental]
hostAddressLoopback=true # Windows can reach services bound to WSL's LAN IPwsl --shutdown # wait ~8 s, then open Ubuntu againip -br addr # your Windows LAN IP (e.g. 192.168.1.50) appears hereOpen a WSL port to your networkDaily development
New-NetFirewallHyperVRule -Name "WSL-8081" -DisplayName "WSL dev 8081" `
-Direction Inbound -VMCreatorId '{40E0AC32-46A5-438A-A0B2-2B479E8F2E90}' `
-Protocol TCP -LocalPorts 8081$ip = (wsl hostname -I).Trim().Split(' ')[0]
netsh interface portproxy add v4tov4 listenport=8081 listenaddress=0.0.0.0 connectport=8081 connectaddress=$ip
New-NetFirewallRule -DisplayName "WSL dev 8081" -Direction Inbound -Protocol TCP -LocalPort 8081 -Action AllowGet-NetFirewallHyperVRule -Name "WSL-8081" | Select-Object Name, Enabled, LocalPortsRemove-NetFirewallHyperVRule -Name "WSL-8081"Test a local HTTPS site on your phoneDaily development
sudo apt install -y mkcert libnss3-tools
mkcert -install # trusts the CA inside Linux
mkcert -CAROOT # folder holding rootCA.pemmkdir -p ~/dev/certs && cd ~/dev/certs
mkcert -cert-file dev.pem -key-file dev-key.pem localhost 127.0.0.1 ::1 192.168.1.50import fs from 'node:fs';
import os from 'node:os';
import { defineConfig } from 'vite';
const certs = `${os.homedir()}/dev/certs`;
export default defineConfig({
server: {
host: true, // listen on all interfaces
https: { key: fs.readFileSync(`${certs}/dev-key.pem`), cert: fs.readFileSync(`${certs}/dev.pem`) },
},
});cp "$(mkcert -CAROOT)/rootCA.pem" /mnt/c/Users/<you>/Downloads/mkcert-rootCA.pemcertutil -user -addstore Root "$env:USERPROFILE\Downloads\mkcert-rootCA.pem"Develop for Android with the emulator on WindowsDaily development
sudo apt install -y adbecho 'export ADB_SERVER_SOCKET=tcp:127.0.0.1:5037' >> ~/.bashrc
source ~/.bashrcadb kill-server
adb -a nodaemon server startexport ADB_SERVER_SOCKET=tcp:$(ip route show default | awk '{print $3}'):5037adb devices # emulator-5554 deviceOpen your Expo app on a phoneDaily development
cd ~/dev/my-app
npx expo startDebug a TypeScript applicationDaily development
pnpm add -D tsx # or: npm i -D tsxpnpm dev # or: npx tsx src/index.ts, pnpm test{
"version": "0.2.0",
"configurations": [
{
"type": "node",
"request": "launch",
"name": "Run current TS file",
"runtimeExecutable": "node",
"runtimeArgs": ["--import", "tsx"],
"program": "${file}",
"skipFiles": ["<node_internals>/**"],
"console": "integratedTerminal"
}
]
}node --inspect-brk --import tsx src/index.tsBack up and restore a development databaseDaily development
mkdir -p ~/dev/backups
devinfra exec -T postgres pg_dump -U dev -Fc my_app > ~/dev/backups/my_app-$(date +%F_%H%M).dumpdevinfra exec -T postgres pg_restore -U dev -d my_app --clean --if-exists --no-owner < ~/dev/backups/my_app-2026-10-06_1430.dumpcp ~/dev/backups/*.dump /mnt/c/Users/<you>/Backups/devinfra exec -T postgres pg_restore -l < ~/dev/backups/my_app-2026-10-06_1430.dump | head # table of contents
devinfra exec postgres psql -U dev -d my_app -c '\dt' # tables after restoreRaise file-watcher limits for large reposDaily development
fs.inotify.max_user_watches=524288 # files watched, across all watchers
fs.inotify.max_user_instances=1024 # separate watcher processes
vm.max_map_count=262144 # only if you run Elasticsearch/OpenSearchsudo sysctl --systemcat /proc/sys/fs/inotify/max_user_watches # 524288
cat /proc/sys/fs/inotify/max_user_instances # 1024sudo rm /etc/sysctl.d/99-dev.confRaise open-file limitsDaily development
printf '* soft nofile 65536\n* hard nofile 524288\n' | sudo tee /etc/security/limits.d/99-dev.conf
sudo mkdir -p /etc/systemd/system.conf.d
printf '[Manager]\nDefaultLimitNOFILE=65536:524288\n' | sudo tee /etc/systemd/system.conf.d/99-nofile.confecho 'fs.file-max=2097152' | sudo tee /etc/sysctl.d/99-file-max.confwsl --shutdownulimit -n # 65536
systemctl show docker -p LimitNOFILE # if Docker is installedsudo rm /etc/security/limits.d/99-dev.conf /etc/systemd/system.conf.d/99-nofile.conf /etc/sysctl.d/99-file-max.confSpeed up installs with Defender exclusionsDaily development
$paths = @("$env:LOCALAPPDATA\wsl") +
(Resolve-Path "$env:LOCALAPPDATA\Packages\CanonicalGroupLimited.Ubuntu*\LocalState" -EA 0).Path
$procs = "vmmemWSL", "vmmem", "wslhost.exe", "wslservice.exe"
$paths | ? { $_ } | % { Add-MpPreference -ExclusionPath $_ }
$procs | % { Add-MpPreference -ExclusionProcess $_ }(Get-MpPreference).ExclusionPath
(Get-MpPreference).ExclusionProcess$paths = @("$env:LOCALAPPDATA\wsl") +
(Resolve-Path "$env:LOCALAPPDATA\Packages\CanonicalGroupLimited.Ubuntu*\LocalState" -EA 0).Path
$paths | ? { $_ } | % { Remove-MpPreference -ExclusionPath $_ }
"vmmemWSL", "vmmem", "wslhost.exe", "wslservice.exe" | % { Remove-MpPreference -ExclusionProcess $_ }Cap WSL memory and CPUDaily development
[wsl2]
memory=16GB # ~50–75% of your RAM
processors=8 # all threads, or all minus 2
swap=8GB # ~25–50% of memory
[experimental]
autoMemoryReclaim=dropcache # give idle RAM back to Windowswsl --shutdown # wait ~8 s, then reopen Ubuntufree -h # "total" ≈ your memory= value
nproc # = processors=Shrink the WSL virtual diskDaily development
df -h / # used vs size inside Linux
docker system df # images, build cache, volumes
docker system prune # unused containers, networks, dangling images
docker builder prune # build cachewsl --shutdown
wsl --manage Ubuntu --set-sparse truesudo systemctl enable --now fstrim.timer
sudo fstrim -avGet-ChildItem "$env:LOCALAPPDATA\wsl", "$env:LOCALAPPDATA\Packages\CanonicalGroupLimited.*" -Recurse -Filter ext4.vhdx -EA 0 |
Select-Object FullName, @{n='GB';e={[math]::Round($_.Length/1GB,1)}}wsl --shutdown
wsl --manage Ubuntu --set-sparse falseKeep WSL, packages and runtimes up to dateDaily development
wsl --update # new WSL + kernel
wsl --version
wsl -l -v # distros and their state
wsl --shutdown # restart into the new versionsudo apt update && sudo apt full-upgrade # system, docker, gh
mise up # runtimes and CLIs from mise
npm i -g wrangler@latest eas-cli@latest # global npm CLIs, if you use themdocker system df # reclaim with: docker system prune
du -xh --max-depth=1 ~ 2>/dev/null | sort -h | tailBack up and restore your WSL distroDaily development
$dir = "$env:USERPROFILE\WSL-Backups"; mkdir $dir -Force | Out-Null
wsl --terminate Ubuntu
wsl --export Ubuntu "$dir\Ubuntu-$(Get-Date -f yyyy-MM-dd_HHmm).tar.gz" --format tar.gzwsl --import Ubuntu-Restored C:\WSL\Ubuntu-Restored "$env:USERPROFILE\WSL-Backups\Ubuntu-<date>.tar.gz"
wsl -d Ubuntu-RestoredGet-ChildItem "$env:USERPROFILE\WSL-Backups" | Sort-Object LastWriteTime | Select-Object -Last 3 Name, Length
wsl -l -v # Ubuntu-Restored appears after an importwsl --unregister Ubuntu-RestoredPhone cannot reach the dev serverFix a problem
ss -ltn 'sport = :5173' # 0.0.0.0 or [::] = reachable; 127.0.0.1 = this machine onlywslinfo --networking-mode # recent WSL; otherwise compare `ip -br addr` with Windows ipconfigipconfig | Select-String IPv4Get-NetFirewallHyperVRule | Where-Object Direction -eq Inbound |
Select-Object Name, DisplayName, LocalPorts, ActionA port is already in useFix a problem
ss -ltnp 'sport = :3000'
sudo lsof -iTCP:3000 -sTCP:LISTEN # shows processes of other users toodocker ps --filter publish=3000Get-NetTCPConnection -LocalPort 3000 -State Listen -EA 0 |
Select-Object LocalAddress, OwningProcess, @{n='Process';e={(Get-Process -Id $_.OwningProcess).ProcessName}}netsh interface ipv4 show excludedportrange protocol=tcpkill <pid> # polite; use kill -9 <pid> only if it ignores thiswsl --shutdown
net stop winnat
net start winnatDNS fails after connecting to a VPNFix a problem
getent hosts github.com # no output = DNS fails
curl -sI https://1.1.1.1 | head -1 # a response = the network itself worksResolve-DnsName github.comcat /etc/resolv.conf
grep -iE 'dnsTunneling|autoProxy|networkingMode' /mnt/c/Users/*/.wslconfig 2>/dev/null[wsl2]
dnsTunneling=true
autoProxy=true # also pick up the VPN's proxy settings[network]
generateResolvConf=falsesudo rm /etc/resolv.conf # it is a symlink WSL created
printf 'nameserver 172.16.0.53\nnameserver 1.1.1.1\n' | sudo tee /etc/resolv.confDocker cannot reach my local APIFix a problem
docker info --format '{{.OperatingSystem}}' # "Docker Desktop" or your Ubuntu versionss -ltn 'sport = :4000' # 127.0.0.1 = WSL only; 0.0.0.0 or [::] = also containersdocker compose exec app getent hosts host.docker.internal
docker compose exec app wget -qO- http://host.docker.internal:4000/health # or curl, if the image has itadb devices shows nothing in WSLFix a problem
adb devicesecho $ADB_SERVER_SOCKET # expect tcp:127.0.0.1:5037
adb version
adb devicesChanges do not trigger hot reloadFix a problem
pwd # starts with /mnt/c/… = on the Windows drivecat /proc/sys/fs/inotify/max_user_watches /proc/sys/fs/inotify/max_user_instancesmkdir -p ~/dev && cd ~/dev
git clone <repo-url>Builds and installs are slowFix a problem
pwd # /mnt/c/… = Windows drive
which node npm pnpm git # any /mnt/c/… path = a Windows binaryfree -h
vmstat 2 5 # non-zero "si"/"so" columns = swappingtime pnpm install --frozen-lockfileToo many open filesFix a problem
ulimit -Sn; ulimit -Hnsystemctl show docker -p LimitNOFILEls /proc/<pid>/fd | wc -lWSL uses too much memoryFix a problem
free -h # "used" = processes; "buff/cache" = file cache Linux can dropps aux --sort=-%mem | head -n 8
docker stats --no-stream 2>/dev/nullGet-Content $env:USERPROFILE\.wslconfig -EA 0Windows disk space is running outFix a problem
Get-ChildItem "$env:LOCALAPPDATA\wsl", "$env:LOCALAPPDATA\Packages\CanonicalGroupLimited.*" -Recurse -Filter ext4.vhdx -EA 0 |
Select-Object FullName, @{n='GB';e={[math]::Round($_.Length/1GB,1)}}df -h /sudo du -xh --max-depth=1 / 2>/dev/null | sort -h | tail -n 8
du -h --max-depth=1 ~ 2>/dev/null | sort -h | tail -n 8
docker system dfNot sure what is wrong? Run a health checkFix a problem
wsl --version # WSL and kernel versions
wsl -l -v # distros, state, and VERSION 2free -h && nproc # matches memory= and processors= in .wslconfig?
systemctl is-system-running # "running", not "degraded"
systemctl --failed
ip -br addr # mirrored mode: same IPs as Windows
getent hosts github.com # DNS works
docker ps # works without sudo (if you use Docker).wslconfig changes do nothingFix a problem
Get-ChildItem $env:USERPROFILE -Force -Filter '.wslconfig*' | Select-Object Name, Length, LastWriteTimewsl -l --runningwsl --shutdownsystemctl says degradedFix a problem
systemctl --failed
journalctl -b -u systemd-binfmt.service --no-pager | tail -n 5 # if it is listedsudo systemctl mask systemd-binfmt.serviceClock is wrong after sleepFix a problem
date
powershell.exe -NoProfile -Command Get-Date 2>/dev/null || /mnt/c/Windows/System32/WindowsPowerShell/v1.0/powershell.exe -NoProfile -Command Get-Datesudo hwclock -s || sudo systemctl restart systemd-timesyncdGPU or CUDA not foundFix a problem
ls /usr/lib/wsl/lib/ | grep -i -E 'cuda|nvidia'
/usr/lib/wsl/lib/nvidia-smi
dpkg -l | grep -i -E 'nvidia-driver|nvidia-dkms' # should print nothingcode, explorer.exe or powershell.exe not foundFix a problem
grep -A3 '^\[interop\]' /etc/wsl.conf
ls /mnt/c/Windows/System32/cmd.exe
cat /proc/sys/fs/binfmt_misc/WSLInterop 2>/dev/null | head -n 1 # "enabled" = Windows .exe files can runnode or npm is the Windows oneFix a problem
which -a node npm npx pnpm
echo "$PATH" | tr ':' '\n' | grep -n -E 'mise|nvm|/mnt/c'sudo -E fails or is ignoredFix a problem
sudo --version | head -n 1 # "sudo-rs …" or "Sudo version 1.9…"sudo env HTTPS_PROXY="$HTTPS_PROXY" FOO=bar some-commandEvery file shows as changedFix a problem
git diff --stat | tail -n 3
git diff | grep -c $'\r' # > 0 = CRLF in the changes
git config --show-origin --get-all core.autocrlf
git config --get core.filemode
pwd # /mnt/c/… = repo on the Windows drivegit config --global core.autocrlf input
printf '* text=auto eol=lf\n' >> .gitattributes
git add --renormalize ..wslconfig settingsConfiguration reference
# Applies after: wsl --shutdown (wait ~8 s, then reopen a terminal)
[wsl2]
memory=16GB # ~50–75% of your RAM
processors=8 # all threads, or all minus 2
swap=8GB # ~25–50% of memory
networkingMode=mirrored # WSL shares Windows' IPs; localhost works both ways
dnsTunneling=true # fixes DNS on VPNs and odd networks
firewall=true # Windows firewall rules apply to WSL
autoProxy=true # use the Windows proxy settings
guiApplications=true # WSLg: run Linux GUI apps
nestedVirtualization=true # KVM / Android emulators inside WSL
[experimental]
autoMemoryReclaim=dropcache # give idle RAM back to Windows
sparseVhd=true # virtual disk shrinks when you delete files
hostAddressLoopback=true # Windows can reach services bound to WSL's LAN IP/etc/wsl.conf settingsConfiguration reference
sudo cp /etc/wsl.conf /etc/wsl.conf.bak[boot]
systemd=true
[user]
default=yourname # also used when you restore a backup
[interop]
enabled=true
appendWindowsPath=true # false = faster shell, but code/explorer.exe disappear
[automount]
enabled=true
options="metadata,umask=22,fmask=11" # real Linux permissions on /mnt/c
[network]
generateResolvConf=true # let WSL manage /etc/resolv.confWhere files liveConfiguration reference
~/dev/
├── projects/ # repos, optionally projects/<org>/<repo>
├── infra/ # shared docker-compose (Postgres, Redis…)
└── sandbox/ # throwaway experimentscp /mnt/c/Users/<you>/Downloads/x.zip ~/dev/sandbox/Networking modesConfiguration reference
ss -tlnp # 0.0.0.0 / [::] = reachable from LAN; 127.0.0.1 = local onlySwitch your shell to zshOptional extras
sudo apt install -y zsh zsh-autosuggestions zsh-syntax-highlightingautoload -Uz compinit && compinit -C
eval "$(~/.local/bin/mise activate zsh)"
source /usr/share/zsh-autosuggestions/zsh-autosuggestions.zsh
source /usr/share/zsh-syntax-highlighting/zsh-syntax-highlighting.zsh # must be lastchsh -s "$(which zsh)"chsh -s /bin/bashA fast prompt with StarshipOptional extras
mkdir -p ~/.local/bin
curl -sS https://starship.rs/install.sh | sh -s -- -b ~/.local/bineval "$(starship init zsh)"Modern command-line toolsOptional extras
sudo apt install -y fzf zoxide direnv eza bat fd-find ripgrep jq btop tmux git-deltamise use -g lazygit atuin dust lazydockerautoload -Uz compinit && compinit -C
eval "$(~/.local/bin/mise activate zsh)"
eval "$(starship init zsh)" # if you use the prompt
eval "$(direnv hook zsh)"
source /usr/share/doc/fzf/examples/key-bindings.zsh
eval "$(atuin init zsh --disable-up-arrow)"
eval "$(zoxide init zsh --cmd cd)" # late: after other cd/chpwd hooks
alias bat=batcat fd=fdfind # Ubuntu package names
alias ll="eza -la --git --group-directories-first"
alias lg=lazygit
source /usr/share/zsh-autosuggestions/zsh-autosuggestions.zsh
source /usr/share/zsh-syntax-highlighting/zsh-syntax-highlighting.zsh # must be lastgit config --global core.pager delta
git config --global delta.navigate trueCall Windows apps without the Windows PATHOptional extras
[interop]
enabled=true
appendWindowsPath=false_win=/mnt/c/Windows
_winuser=$(wslpath "$($_win/System32/cmd.exe /c 'echo %USERPROFILE%' 2>/dev/null | tr -d '\r')")
_apps="$_winuser/AppData/Local/Programs"
[ -d "$_apps/Microsoft VS Code/bin" ] && export PATH="$PATH:$_apps/Microsoft VS Code/bin"
[ -d "$_apps/cursor/resources/app/bin" ] && export PATH="$PATH:$_apps/cursor/resources/app/bin"
alias open="$_win/explorer.exe" # open . → Explorer here
alias clip="$_win/System32/clip.exe" # echo hi | clip
alias pwsh="$_win/System32/WindowsPowerShell/v1.0/powershell.exe"
alias winget="$_winuser/AppData/Local/Microsoft/WindowsApps/winget.exe"
unset _win _winuser _appsmkdir -p ~/.config/shell
echo 'source ~/.config/shell/common.sh' >> ~/.bashrcMake Windows Terminal open in LinuxOptional extras
wsl.exe -d Ubuntu --cd ~Small conveniencesOptional extras
sudo apt install -y wslu
echo 'export BROWSER=wslview' >> ~/.bashrc # or ~/.zshrc