Windows with WSL2
Windows Subsystem for Linux 2 (WSL2) lets you run a real Ubuntu environment directly on Windows — no virtual machine overhead, full Docker support, and native filesystem performance for your project files. It is the recommended path for serious monorepo development on Windows.
- Windows 10 version 2004 (Build 19041) or later, or Windows 11
- At least 16 GB of RAM recommended (8 GB minimum)
- Virtualisation enabled in BIOS/UEFI (usually on by default on modern hardware)
1. Enable WSL2 and install Ubuntu​
Open PowerShell as Administrator and run:
wsl --install
This single command enables the WSL feature, installs the WSL2 kernel, sets WSL2 as the default version, and installs Ubuntu (the default distribution) from the Microsoft Store.
After the command completes, restart your machine. Ubuntu will finish its first-run setup on reboot and prompt you to create a UNIX username and password.
If you already have WSL1 distributions installed, upgrade them and set the default version:
wsl --set-default-version 2
wsl --set-version Ubuntu 2
Verify after reboot:
wsl --status # should show "Default Version: 2"
wsl --list --verbose # should show Ubuntu with VERSION 2
2. Configure memory and CPU limits​
By default WSL2 uses up to 50 % of your total RAM (capped at 8 GB on Windows 10). For monorepo development with build tools, search indexing, and multiple services running simultaneously, raise the limit to at least 8 GB, 16 GB if your machine allows it.
Create (or edit) C:\Users\<YourUsername>\.wslconfig in Notepad or any Windows text editor:
[wsl2]
# Raise to at least 8 GB; 16 GB is recommended for full monorepo dev
memory=16GB
# Optional: limit CPU cores (defaults to all logical cores)
processors=8
# Optional: swap size (defaults to 25 % of memory)
swap=8GB
# Windows 11 22H2+ / WSL 2.0+: mirror Windows networking so that
# "localhost" inside WSL2 maps directly to the Windows host.
# Remove this line if you are on Windows 10 or an older WSL build.
networkingMode=mirrored
Apply the new config by restarting WSL:
wsl --shutdown
wsl
networkingMode=mirrored is available in WSL 2.0+ (bundled with Windows 11 22H2 and later). With it enabled, localhost inside WSL2 resolves directly to the Windows host — so a PostgreSQL instance running on Windows port 5432 is reachable as localhost:5432 from WSL2 without any extra configuration.
If you are on Windows 10 or an older WSL build, omit that line and use the Windows host IP approach described later.
3. Open your Ubuntu terminal​
Launch Ubuntu from the Start menu, or from any PowerShell / Command Prompt:
wsl
All remaining commands in this guide are run inside the Ubuntu shell unless noted otherwise.
4. Install WSL utilities (wslu)​
wslu is the official WSL utility package. Its most useful component is wslview, which opens URLs and files in your Windows default browser or application. Many developer CLI tools (including gh auth login) use xdg-open internally — wslu plugs the WSL-specific "hand it to Windows" behaviour into the standard xdg-open contract.
sudo apt update
sudo apt install wslu
Verify:
which wslview # should print /usr/bin/wslview
xdg-open https://example.com # should open in your Windows default browser
On recent Ubuntu WSL images wslu is often pre-installed. If which wslview returns a path, you already have it.
If you prefer not to install wslu, point the BROWSER environment variable at your Windows browser. Add one of the following to ~/.bashrc:
# Option A — let Windows pick the default browser
export BROWSER="powershell.exe /c start"
# Option B — hardcode a specific browser
export BROWSER="/mnt/c/Program Files/Google/Chrome/Application/chrome.exe"
# Edge lives at: /mnt/c/Program Files (x86)/Microsoft/Edge/Application/msedge.exe
Many CLI tools (including gh) respect $BROWSER before falling through to xdg-open.
5. Install Node.js 26​
Use nvm to manage Node versions — it installs into your user directory and requires no sudo:
curl -o- https://raw.githubusercontent.com/nvm-sh/nvm/v0.40.0/install.sh | bash
Close and reopen your terminal (or source ~/.bashrc), then:
nvm install 26
nvm use 26
nvm alias default 26
Verify: node --version → v24.x.x
Install build tools for native Node modules​
Several dependencies (e.g. better-sqlite3, isolated-vm) ship without prebuilt binaries and must be compiled locally via node-gyp. On a fresh Ubuntu WSL2 image you need a C/C++ toolchain and Python:
sudo apt update
sudo apt install -y build-essential python3
Run this before yarn install. If yarn install ran before these tools were available, native modules silently fail to build and Yarn caches the failure — symptoms include Could not locate the bindings file for better-sqlite3 and the cache service warning [cache] sqlite strategy unavailable (better-sqlite3 failed to load). Falling back to memory strategy.. To recover, install the toolchain and then force a rebuild:
yarn rebuild
6. Install Yarn 4.12.0​
Corepack ships with Node.js. Enable it once per machine, then activate Yarn:
corepack enable
corepack prepare yarn@4.12.0 --activate
Verify: yarn --version → 4.12.0
7. Install GitHub CLI and authenticate​
Install gh from the official apt repository:
sudo apt update && sudo apt install gh -y
If the gh version in the Ubuntu package mirrors is outdated, use the official GitHub CLI repository:
type -p curl >/dev/null || (sudo apt update && sudo apt install curl -y)
curl -fsSL https://cli.github.com/packages/githubcli-archive-keyring.gpg \
| sudo dd of=/usr/share/keyrings/githubcli-archive-keyring.gpg \
&& sudo chmod go+r /usr/share/keyrings/githubcli-archive-keyring.gpg \
&& echo "deb [arch=$(dpkg --print-architecture) signed-by=/usr/share/keyrings/githubcli-archive-keyring.gpg] https://cli.github.com/packages stable main" \
| sudo tee /etc/apt/sources.list.d/github-cli.list > /dev/null \
&& sudo apt update \
&& sudo apt install gh -y
Authenticate with GitHub:
gh auth login
gh will print a one-time code and a URL, then try to open your browser. With wslu installed (step 4), the browser opens automatically. Without it, copy the URL (https://github.com/login/device) into your Windows browser manually, enter the code, then return to the terminal — gh keeps waiting and completes the flow once you confirm in the browser.
Verify: gh auth status
8. Infrastructure services​
Choose one option for running PostgreSQL, Redis, and Meilisearch.
Option A — Docker Desktop with WSL2 backend (recommended)​
Install Docker Desktop for Windows. During setup (or in Settings → General) enable "Use the WSL 2 based engine" and in Settings → Resources → WSL integration enable integration for your Ubuntu distribution.
After Docker Desktop is running, docker and docker compose are available inside WSL2 without any additional installation. Start all services from the repository root:
docker compose up -d
Verify: docker compose ps — all three services (postgres, redis, meilisearch) should be Up.
Option B — Docker Engine natively inside WSL2​
If you prefer not to install Docker Desktop, install the Docker Engine directly inside Ubuntu:
sudo apt update
sudo apt install -y ca-certificates curl gnupg
sudo install -m 0755 -d /etc/apt/keyrings
curl -fsSL https://download.docker.com/linux/ubuntu/gpg \
| sudo gpg --dearmor -o /etc/apt/keyrings/docker.gpg
sudo chmod a+r /etc/apt/keyrings/docker.gpg
echo \
"deb [arch=$(dpkg --print-architecture) signed-by=/etc/apt/keyrings/docker.gpg] \
https://download.docker.com/linux/ubuntu \
$(. /etc/os-release && echo "$VERSION_CODENAME") stable" \
| sudo tee /etc/apt/sources.list.d/docker.list > /dev/null
sudo apt update
sudo apt install -y docker-ce docker-ce-cli containerd.io docker-buildx-plugin docker-compose-plugin
# Allow running docker without sudo
sudo usermod -aG docker $USER
newgrp docker
Start the Docker daemon (WSL2 does not run systemd by default on Ubuntu 22.04 and earlier):
sudo service docker start
On Ubuntu 22.04+ you can enable systemd in WSL2. Add to /etc/wsl.conf:
[boot]
systemd=true
Then restart WSL (wsl --shutdown from PowerShell). After that, Docker starts automatically on WSL boot.
Start all services:
docker compose up -d
9. Connecting WSL2 to a Windows-hosted database​
If you prefer to run PostgreSQL natively on Windows (e.g. via the EDB installer with pgAdmin 4) and connect to it from WSL2, follow these steps.
Windows 11 / WSL 2.0+ with networkingMode=mirrored​
If you set networkingMode=mirrored in .wslconfig (step 2), localhost inside WSL2 maps directly to the Windows host. No extra configuration is needed:
DATABASE_URL=postgres://postgres:<password>@localhost:5432/saasframe
Windows 10 / older WSL2 (NAT networking)​
Without mirrored networking, WSL2 gets a private virtual IP and the Windows host is reachable via a gateway IP that changes on each WSL boot. Obtain it at runtime:
WINDOWS_HOST=$(cat /etc/resolv.conf | grep nameserver | awk '{print $2}')
echo $WINDOWS_HOST # e.g. 172.28.160.1
Set DATABASE_URL using that IP:
DATABASE_URL=postgres://postgres:<password>@172.28.160.1:5432/saasframe
For a persistent connection string you can export the variable in ~/.bashrc:
echo 'export WINDOWS_HOST=$(cat /etc/resolv.conf | grep nameserver | awk '"'"'{print $2}'"'"')' >> ~/.bashrc
Allow WSL2 connections in PostgreSQL on Windows​
By default PostgreSQL on Windows only accepts connections from localhost (127.0.0.1). You need to allow the WSL2 subnet.
-
Find PostgreSQL's data directory (e.g.
C:\Program Files\PostgreSQL\17\data\). -
Edit
pg_hba.conf— add a line for the WSL2 subnet (typically172.16.0.0/12):# WSL2 connectionshost all all 172.16.0.0/12 scram-sha-256 -
Edit
postgresql.conf— ensure the server listens on all interfaces:listen_addresses = '*' -
Restart the PostgreSQL service from an Administrator PowerShell:
Restart-Service postgresql-x64-17 # adjust version number as needed -
Open port 5432 in Windows Firewall for the WSL2 subnet (optional, but required if the firewall blocks it):
New-NetFirewallRule -DisplayName "PostgreSQL WSL2" -Direction Inbound `-LocalPort 5432 -Protocol TCP -Action Allow `-RemoteAddress 172.16.0.0/12
10. Clone and configure​
git clone https://github.com/saasframe/saasframe.git
cd saasframe
git checkout develop
Copy the environment template:
cp apps/saasframe/.env.example apps/saasframe/.env
Edit apps/saasframe/.env:
DATABASE_URL=postgres://postgres:postgres@localhost:5432/saasframe
JWT_SECRET=change-me-dev-secret
REDIS_URL=redis://localhost:6379
Generate a strong JWT_SECRET:
openssl rand -hex 32
For best performance, clone the repository inside the WSL2 filesystem (~/projects/saasframe) rather than on the Windows drive (/mnt/c/...). Cross-filesystem I/O is significantly slower and can cause file-watch issues with Next.js.
11. Install dependencies and bootstrap​
yarn install
yarn build:packages
yarn generate
yarn build:packages # second run required — compiles generated scripts
yarn initialize
yarn initialize runs migrations, seeds roles, provisions an admin user, and loads demo CRM data. Add --no-examples to skip demo content. The admin credentials are printed at the end.
12. Start the app​
yarn dev
Open http://localhost:3000/backend in your Windows browser and sign in with the credentials printed by yarn initialize.
The compact runtime also serves a splash page at http://localhost:4000 with live startup progress. Press d while yarn dev is running to toggle raw log output.
If startup stops with OS file watch limit reached, raise WSL's Linux inotify limits and start again:
yarn dev:fix-wsl-watchers
yarn dev
The command asks before applying sysctl changes, then sets the current WSL session limits and writes /etc/sysctl.d/99-saasframe-inotify.conf so the values survive future WSL restarts. It may ask for your Ubuntu sudo password. It also offers an optional VS Code workspace-settings update to exclude generated/cache/worktree folders from editor file watching; if you accept it while VS Code is already open, run Developer: Reload Window from the command palette so VS Code releases old watcher handles.
Upgrading an existing checkout​
git pull
yarn install
yarn db:migrate
yarn generate
yarn dev
Accessing the app from Windows​
With networkingMode=mirrored (Windows 11 / WSL 2.0+), services bound to localhost inside WSL2 are directly accessible from Windows at http://localhost:<port>.
Without mirrored networking, find the WSL2 IP from inside WSL2:
hostname -I | awk '{print $1}' # e.g. 172.28.163.55
Then open http://172.28.163.55:3000/backend in your Windows browser.
Editing from Windows with VS Code or Visual Studio​
VS Code (recommended)​
VS Code's WSL extension turns VS Code on Windows into a thin client: the UI runs on Windows, but the language servers, terminal, extensions, and file I/O all run inside WSL2. That means no /mnt/c slowdown, and tools like node, yarn, docker, and gh are the ones you installed in Ubuntu.
-
Install VS Code for Windows.
-
Install the WSL extension (
ms-vscode-remote.remote-wsl) from the Extensions pane, or from PowerShell:code --install-extension ms-vscode-remote.remote-wsl -
From your Ubuntu terminal, open the repository in VS Code:
cd ~/projects/saasframecode .The first run downloads the VS Code server into WSL2 (one-off, ~100 MB). Subsequent launches are instant.
The bottom-left status bar should read WSL: Ubuntu. The integrated terminal (Ctrl+`) drops you straight into the Ubuntu shell with node, yarn, docker, etc. already on PATH.
Install these inside the WSL target (click "Install in WSL: Ubuntu" on each extension's page — installing them on the Windows side does nothing for WSL projects):
- ESLint (
dbaeumer.vscode-eslint) - Prettier (
esbenp.prettier-vscode) - Tailwind CSS IntelliSense (
bradlc.vscode-tailwindcss) - Docker (
ms-azuretools.vscode-docker) - GitLens (
eamodio.gitlens)
code . from WSL, not via File → Open Folder on \\wsl$\...Opening the repo through the \\wsl$\Ubuntu\home\... UNC path makes VS Code treat it as a Windows-side folder: extensions run on Windows, file watching goes through the 9P filesystem bridge, and performance collapses. Always open via code . from inside the Ubuntu shell, or use the WSL: Open Folder in WSL… command from the command palette.
Visual Studio 2022​
Visual Studio 2022 has native WSL support for C++/CMake and .NET/Linux projects, but it is not a good fit for this monorepo (Next.js + TypeScript + Yarn 4). Use VS Code with the WSL extension instead.
If you still want to browse the source from Visual Studio, you can open the folder via \\wsl$\Ubuntu\home\<you>\projects\saasframe — but builds, yarn, and debugging must be run from the Ubuntu terminal, not from Visual Studio.
Common issues​
| Symptom | Fix |
|---|---|
Failed opening a web browser during gh auth login | Install wslu (sudo apt install wslu) or set export BROWSER="powershell.exe /c start". The auth flow still works: copy the printed URL into your Windows browser manually, enter the one-time code, then return to the terminal. |
Out of memory during yarn build:packages | Increase memory= in .wslconfig and run wsl --shutdown to restart WSL. |
| File changes not detected by Next.js watch | Your project files must be inside the WSL2 filesystem (~/...), not on /mnt/c/.... |
OS file watch limit reached from Turbopack | Run yarn dev:fix-wsl-watchers, reload the WSL VS Code window if it is open, then restart yarn dev. |
| Cannot connect to Windows-hosted PostgreSQL | See step 9. Check pg_hba.conf, listen_addresses, and Windows Firewall. |
docker: command not found after install | Run newgrp docker or log out and back into your WSL2 session to pick up group membership. |
| WSL2 uses too much memory | Set memory= in .wslconfig, restart with wsl --shutdown. |
[cache] sqlite strategy unavailable (better-sqlite3 failed to load) or Could not locate the bindings file | The native module never built. Install the toolchain (sudo apt install -y build-essential python3) then rebuild: yarn rebuild better-sqlite3. See step 5. |