WSL2 gives Windows developers a real Linux shell, which matters when your team’s scripts, Dockerfiles and CI all assume bash. For Capacitor it works well, with one rule: keep the web toolchain in WSL2 and keep the Android emulator, USB devices and Android Studio on Windows. Then bridge the two with adb.
This guide shows that setup, the networking details that trip people up, and how iOS fits in through Capgo Build.
When WSL2 is worth it
Use WSL2 when:
- Your repo has bash scripts, Makefiles or Husky hooks that break in PowerShell.
- You run the same Docker images locally and in CI.
- You want the Linux
node_moduleslayout so lockfiles match your Linux CI.
Skip WSL2 when you only need Android Studio and a terminal. The native Windows guide is simpler in that case.
1) Install WSL2 and a distribution
From an elevated PowerShell:
wsl --install -d Ubuntu
Reboot, open Ubuntu, create your Linux user. Update and install the basics:
sudo apt update && sudo apt install -y build-essential unzip openjdk-21-jdk
curl -fsSL https://bun.sh/install | bash
2) Keep the project in the Linux file system
Create your projects under your Linux home, not under /mnt/c:
mkdir -p ~/dev && cd ~/dev
bun create vite@latest my-app
cd my-app
bun install
Access the files from Windows tools through \\wsl$\Ubuntu\home\you\dev\my-app. VS Code with the WSL extension opens it directly with code ..
Why this matters: cross-file-system access through /mnt/c is slow for the thousands of small files in node_modules, and inotify watchers used by Vite do not fire reliably for files on the Windows drive.
3) Decide where the Android SDK lives
Two workable layouts:
Layout A (recommended): SDK on Windows, Gradle in WSL2 pointing at a Linux SDK copy. Install Android Studio on Windows for the emulator and GUI tools, and install a separate command line SDK inside WSL2 for Gradle:
mkdir -p ~/Android/Sdk/cmdline-tools && cd ~/Android/Sdk/cmdline-tools
# download commandlinetools-linux-*.zip from the Android developer site
unzip commandlinetools-linux-*.zip && mv cmdline-tools latest
echo 'export ANDROID_HOME=$HOME/Android/Sdk' >> ~/.bashrc
echo 'export PATH=$PATH:$ANDROID_HOME/cmdline-tools/latest/bin:$ANDROID_HOME/platform-tools' >> ~/.bashrc
source ~/.bashrc
sdkmanager "platform-tools" "platforms;android-36" "build-tools;36.0.0"
sdkmanager --licenses
Yes, it duplicates a few hundred megabytes. In exchange, Gradle runs at Linux speed and never touches /mnt/c.
Layout B: everything on Windows, WSL2 only for the web build. You run bun run build in WSL2 and cap sync plus gradlew from PowerShell on the same files through \\wsl$. It works but is slower and mixes two shells. Only pick it if you cannot install anything in WSL2.
The rest of this guide assumes Layout A.
4) Bridge adb between WSL2 and Windows
The emulator and USB phones are attached to Windows. The WSL2 Gradle build and cap run android need to reach them. Keep a single adb server on Windows and let WSL2 be a client.
On Windows, start the adb server listening on all interfaces. Use the adb from the Windows SDK:
adb kill-server
adb -a -P 5037 nodaemon server
Leave that window open. Allow port 5037 through the Windows firewall for the WSL virtual adapter if prompted.
In WSL2, point the adb client at the Windows host:
export WSL_HOST=$(ip route show default | awk '{print $3}')
export ADB_SERVER_SOCKET=tcp:$WSL_HOST:5037
adb devices
The emulator or your phone now shows up inside WSL2. The two adb binaries must be the same major version, so update platform-tools on both sides at the same time.
Mirrored networking simplifies this
On Windows 11 22H2 or newer, enable mirrored networking so WSL2 shares the Windows network stack. Create or edit %USERPROFILE%\.wslconfig:
[wsl2]
networkingMode=mirrored
Run wsl --shutdown, reopen Ubuntu, then use ADB_SERVER_SOCKET=tcp:127.0.0.1:5037. The host IP no longer changes at every reboot, and live reload gets simpler too.
Alternative: pass the USB device into WSL2
If you want adb fully inside WSL2 for a physical phone, use usbipd-win:
winget install usbipd
usbipd list
usbipd bind --busid 2-3
usbipd attach --wsl --busid 2-3
Then add the udev rule from the Linux guide inside WSL2. This does not help for the emulator, which stays a network connection.
5) Run the app
With adb bridged, Capacitor works like on Linux:
bun add @capacitor/core @capacitor/android @capacitor/ios
bun add -d @capacitor/cli
bunx cap init
bun run build
bunx cap add android
bunx cap add ios
bunx cap run android
cap open android wants Android Studio on the same machine. Instead, open the android/ folder in Windows Android Studio through \\wsl$\Ubuntu\home\you\dev\my-app\android. Studio’s bundled Gradle then builds on Windows while your terminal builds in WSL2; both share the same source files.
6) Live reload across the boundary
Vite runs in WSL2, the app runs on a Windows emulator or a phone. The emulator must reach the dev server.
Start Vite bound to all interfaces:
bun run dev -- --host 0.0.0.0
Point Capacitor at it in capacitor.config.ts:
const config: CapacitorConfig = {
appId: 'com.example.app',
appName: 'my-app',
webDir: 'dist',
server: {
url: 'http://10.0.2.2:5173',
cleartext: true,
},
};
10.0.2.2 is the emulator’s alias for the host machine. With mirrored networking the WSL2 port is visible on the Windows host, so this address reaches Vite. Without mirrored networking, forward the port on Windows first:
netsh interface portproxy add v4tov4 listenport=5173 listenaddress=0.0.0.0 connectport=5173 connectaddress=$(wsl hostname -I)
For a USB phone, replace the URL with your Windows LAN IP and allow port 5173 through the Windows firewall. Run bunx cap sync android after changing the config, and remove the server block before building a release.
7) iOS from WSL2 with Capgo Build
Xcode needs macOS, so WSL2 handles everything up to the native compile and Capgo does the rest:
bun run build
bunx cap sync ios
bunx @capgo/cli@latest login
bunx @capgo/cli@latest build init --platform ios
bunx @capgo/cli@latest build request com.example.app --platform ios --build-mode release
The CLI uploads the prepared ios/ project and streams logs back into your WSL2 terminal. Signing material is created without a Mac, with OpenSSL or the iOS certificate generator. Full details are in Build an iOS app from Linux with Capacitor and Capgo Build, which applies to WSL2 unchanged.
8) Avoid native builds for daily changes
Once both apps are in the stores, Capgo Live Updates push web changes from WSL2 without touching Gradle or Capgo Build:
bunx @capgo/cli@latest bundle upload --channel production
Common WSL2 issues
adb: device offlineor empty list in WSL2: the Windows adb server is not running with-a, or the versions differ. Restart it and compareadb versionon both sides.- Host IP changed after reboot: expected without mirrored networking. Put the
WSL_HOSTexport in~/.bashrcso it is recomputed, or switch to mirrored mode. - Vite does not see file changes: project is under
/mnt/c. Move it to the Linux home. - Gradle extremely slow: same cause, or the Gradle cache is on
/mnt/c. Keep~/.gradlein WSL2. - Clock drift breaks Apple API authentication during
build init: WSL2 clocks drift after sleep. Runsudo hwclock -sand retry. cap open androidfails: Studio is on Windows. Open the folder from Studio instead.
Summary
WSL2 is a good home for the Capacitor web toolchain when your team lives in bash. Keep the emulator and devices on Windows, bridge them with one adb server, keep files in the Linux file system, and let Capgo Build take the iOS compile. The result is a Linux workflow on a Windows laptop that still ships to both app stores.