The Day I Stopped Dreaming About a MacBook
Back in 2015, when I first started learning to code, all the senior developers around me carried MacBooks. I was a broke college freshman with a second-hand Samsung laptop running Windows. I wanted to code at Starbucks like "real developers," but somehow, bringing a Windows laptop felt like showing up to a chef's convention with instant ramen.
The problem wasn't vanity; it was the development environment. To learn backend development, I needed a Unix-like system. Windows wasn't Unix. Basic tools like bash, curl, grep, and ssh weren't available out of the box. You had to cobble together half-baked solutions like Cygwin or Git Bash. Servers ran Ubuntu, but I was developing on Windows, which led to countless "works on my machine" situations.
So I chose dual booting. I split my hard drive in half—Windows on one side, Ubuntu on the other. Gaming or writing documents? Boot Windows. Coding? Reboot into Ubuntu. It was the peak of inconvenience. Some days I rebooted my laptop five times. The Windows startup sound became my nemesis.
Later, I tried VMware Workstation. Running Ubuntu as a virtual machine meant no rebooting, but it was painfully slow. On my 4GB laptop, allocating 2GB to the VM meant both Windows and Linux crawled. Running a compile would make the fan scream like it was auditioning for a horror movie.
"When I make money, I'm buying a MacBook."
That was the collective lament of every Windows-using beginner developer.
Why Microsoft Suddenly Fell in Love with Linux
Then, in 2016, Microsoft made a bizarre announcement: they were building something called Windows Subsystem for Linux (WSL). The idea was to let you run native Linux binaries on Windows. Everyone was suspicious. "Microsoft supporting Linux? What's the catch?" Historically, Microsoft had been hostile to Linux and open source.
But this wasn't a trap—it was a strategic pivot. Microsoft realized that to grow Azure in the cloud era, they needed to keep developers on Windows. If local development moved to Mac or Linux while servers ran Linux, the Windows ecosystem would die. CEO Satya Nadella declared "Microsoft loves Linux," and it wasn't empty rhetoric—over half the VMs running on Azure were Linux.
That's how WSL was born. WSL 1, released in 2016, was an interesting experiment, but it had serious problems.
WSL 1: Hiring a Translator
To understand WSL 1's architecture, you need to grasp the concept of a translation layer. Linux programs use Linux system calls (syscalls)—functions like open(), read(), write(). Windows has a completely different kernel structure.
WSL 1 worked by translating Linux syscalls into Windows API calls in real-time. Like an interpreter. When a Linux program said "open this file," WSL caught it and translated it to "Hey Windows NT kernel, please open this file."
This was an impressive engineering feat, but it had fundamental limitations:
- Compatibility issues: Perfectly translating all syscalls was impossible. Linux has over 300 syscalls, and some had no Windows equivalent.
- Performance overhead: The translation process itself was slow, especially for file-heavy operations.
- Docker didn't work: Docker relies on Linux kernel features (cgroups, namespaces). WSL 1 wasn't a real Linux kernel, so Docker was a no-go.
I tried WSL 1 and thought, "This is just slightly better Git Bash." The hype was high; the disappointment was higher.
WSL 2: Planting a Real Kernel
When Microsoft announced WSL 2 in 2019, my reaction was, "Here we go again, what's different now?" But when I actually tried it, I was shocked. This was a completely different beast.
The core idea of WSL 2 is simple: Stop translating. Run a real Linux kernel.
Microsoft took the Linux kernel source code (it's open source, after all), customized it for Windows, and ran it in a lightweight virtual machine using Hyper-V, their hypervisor. This is the lightweight VM strategy.
The difference from traditional VMs:
- Boot time: Under 1 second. Traditional VMs took 30 seconds to a minute.
- Memory management: Dynamic allocation. When you're not using it, memory goes back to Windows.
- File system integration: Windows files and Linux files can see each other.
- Network integration: localhost is shared.
It's like pitching a small tent inside your house. Inside the tent (Linux), it's a completely independent space, but you can still use the whole house (Windows). You share the bathroom (file system) and the Wi-Fi (network).
WSL 1 vs WSL 2 Architecture Comparison
WSL 1 structure:
[Linux App]
↓ (syscall)
[WSL Translation Layer]
↓ (convert)
[Windows NT Kernel]
WSL 2 structure:
[Linux App]
↓ (syscall)
[Real Linux Kernel (in VM)]
↕ (Hyper-V)
[Windows NT Kernel]
See the difference? WSL 2 has a real Linux kernel. No translation.
Setting Up WSL 2 for Real Development
Now let me walk you through actually installing WSL 2 and setting up a Node.js development environment. This is my actual setup.
Step 1: Installing WSL 2
If you're on Windows 10 version 2004 or later, it's a one-liner:
wsl --install
This command:
- Enables WSL features
- Installs Hyper-V platform
- Downloads and installs Ubuntu distribution
- Sets default WSL version to 2
Reboot your computer, and Ubuntu installation completes. It asks for a username and password—this is your Linux account, separate from your Windows account.
If you want a specific distribution:
# List available distributions
wsl --list --online
# Install Ubuntu 22.04
wsl --install -d Ubuntu-22.04
Step 2: Updates and Essential Tools
First login to Ubuntu? Update packages:
sudo apt update && sudo apt upgrade -y
# Install essential dev tools
sudo apt install -y \
build-essential \
curl \
wget \
git \
vim \
zsh
Step 3: Zsh and Oh My Zsh Setup
Install zsh (more powerful than bash) and make it pretty with Oh My Zsh:
# Set zsh as default shell
chsh -s $(which zsh)
# Install Oh My Zsh
sh -c "$(curl -fsSL https://raw.githubusercontent.com/ohmyzsh/ohmyzsh/master/tools/install.sh)"
# Add useful plugins
git clone https://github.com/zsh-users/zsh-autosuggestions ${ZSH_CUSTOM:-~/.oh-my-zsh/custom}/plugins/zsh-autosuggestions
git clone https://github.com/zsh-users/zsh-syntax-highlighting ${ZSH_CUSTOM:-~/.oh-my-zsh/custom}/plugins/zsh-syntax-highlighting
# Edit .zshrc to enable plugins
vim ~/.zshrc
# plugins=(git zsh-autosuggestions zsh-syntax-highlighting)
Restart your terminal and zsh kicks in. Command autocompletion actually works now.
Step 4: Building a Node.js Development Environment
Best practice is to use NVM (Node Version Manager):
# Install NVM
curl -o- https://raw.githubusercontent.com/nvm-sh/nvm/v0.39.0/install.sh | bash
# Restart terminal or
source ~/.zshrc
# Install Node.js LTS
nvm install --lts
nvm use --lts
# Verify
node --version
npm --version
# Install global packages
npm install -g yarn pnpm typescript ts-node nodemon
Now let's create an actual project:
# Work from home directory inside WSL (important!)
cd ~
mkdir projects
cd projects
# Create Express.js project
mkdir my-api
cd my-api
npm init -y
npm install express
# Write server.js
cat > server.js << 'EOF'
const express = require('express');
const app = express();
const port = 3000;
app.get('/', (req, res) => {
res.json({
message: 'Hello from WSL2!',
platform: process.platform,
hostname: require('os').hostname()
});
});
app.listen(port, () => {
console.log(`Server running at http://localhost:${port}`);
});
EOF
# Run it
node server.js
Open http://localhost:3000 in your Windows browser and you get a response. A Linux server talking to a Windows browser. This works because networking is integrated.
Step 5: VS Code Integration
This is where WSL gets truly magical:
# From WSL terminal
cd ~/projects/my-api
code .
Type that command and Windows VS Code opens, editing files inside WSL. VS Code automatically installs the "Remote - WSL" extension and actually runs a VS Code Server inside WSL. The GUI is in Windows, but the code execution is in Linux.
The terminal integrates too. Hit Ctrl+` and you get a WSL bash/zsh terminal. The debugger works. Extensions install. This is a native Linux development experience.
File Systems: Understanding the Performance Core
The biggest mistake people make with WSL 2 is where they put their files. This can make a 10x performance difference.
Two File Systems
WSL 2 has two file systems:
- Linux file system (ext4): The real Linux area inside WSL. Paths like
/home/username/. - Windows file system (NTFS): Your Windows drives. Mounted in WSL as
/mnt/c/Users/....
You can access Windows files from WSL and vice versa:
# Access Windows C drive from WSL
cd /mnt/c/Users/RATIA/Desktop
ls
# Access WSL from Windows Explorer
# In address bar: \\wsl$\Ubuntu\home\username\
The Performance Trap: Cross-filesystem Access
Here's where massive performance differences happen:
- Within same file system: Fast (native speed)
- Cross-filesystem operations: Slow (10-100x slower)
For example, if you put a Node.js project in /mnt/c/Users/RATIA/projects/ and run npm install from WSL:
- npm runs in Linux
- Files write to NTFS
- Every file access crosses the VM boundary
Result? Painfully slow. npm install can take 5 minutes.
Solution: Put projects inside WSL.
# Bad: Windows file system
cd /mnt/c/projects/my-app # Slow
# Good: Linux file system
cd ~/projects/my-app # Fast
When files are in ~/ (Linux home), you're using ext4 directly. npm install takes under a minute.
Backing Up WSL Files from Windows
WSL files are accessible from Windows Explorer at \\wsl$\Ubuntu\home\username\. Create a shortcut to this location. You can copy files to Windows for backup.
Docker Desktop and the WSL 2 Backend
Docker is WSL 2's killer app. Before WSL 2, running Docker on Windows meant Docker Desktop had to spin up a separate Linux VM using VirtualBox or Hyper-V. Heavy and slow.
After WSL 2, Docker Desktop can use WSL 2 as its backend. This was a game changer:
- Install Docker Desktop with "Use WSL 2 based engine" enabled
- Docker daemon runs inside WSL 2
- Same
dockercommands work in Windows PowerShell and WSL
# From WSL
docker run -d -p 8080:80 nginx
# Open http://localhost:8080 in Windows browser → nginx works
Docker containers run inside WSL at native Linux speed. Kubernetes (kind, k3s) works great in WSL too.
For me, spinning up PostgreSQL + Redis + API server with Docker Compose used to take 5 minutes pre-WSL. Now it takes under 30 seconds.
Networking: Is localhost Actually Magic?
WSL 2 is technically a VM, but networking is configured in NAT mode. What this means:
- Server on
localhost:3000in WSL? Windows can access it atlocalhost:3000 - Proxy or DB on Windows? WSL can access it
Most of the time, "it just works." But occasionally:
Problem: Accessing Windows Host from WSL
To access a service running on Windows (like PostgreSQL) from WSL, sometimes you need the Windows IP instead of localhost:
# Check Windows IP in WSL's /etc/resolv.conf
cat /etc/resolv.conf | grep nameserver
# nameserver 172.18.144.1
# Use that IP
psql -h 172.18.144.1 -U postgres
Or create an alias in .bashrc/.zshrc:
export WINDOWS_HOST=$(cat /etc/resolv.conf | grep nameserver | awk '{print $2}')
GPU Passthrough: Great News for AI Developers
WSL 2 supports direct NVIDIA GPU access. This is huge for deep learning developers using CUDA.
Setup:
- Install NVIDIA GPU driver on Windows (CUDA-enabled version)
- Inside WSL, only install CUDA toolkit
# Check GPU in WSL
nvidia-smi
# NVIDIA GPU appears!
# Install PyTorch
pip install torch torchvision torchaudio --index-url https://download.pytorch.org/whl/cu118
# Test in Python
python -c "import torch; print(torch.cuda.is_available())" # True
TensorFlow and PyTorch leverage the GPU directly. Game on Windows, train models in WSL—both possible.
systemd Support: Like Real Linux
WSL used to not support systemd. This was a problem because many Linux services (PostgreSQL, Redis, nginx) are managed by systemd.
Since 2022, WSL officially supports systemd:
# Create /etc/wsl.conf
sudo tee /etc/wsl.conf > /dev/null <<EOF
[boot]
systemd=true
EOF
# Restart WSL
# From PowerShell
wsl --shutdown
wsl
Now you can manage services like real Linux:
sudo systemctl status ssh
sudo systemctl enable docker
sudo systemctl start postgresql
WSLg: GUI Apps Too
In 2021, WSLg was added, enabling Linux GUI applications in WSL. No X11 server installation needed—it just works.
# Install gedit
sudo apt install gedit
# Run it
gedit &
# A GUI window pops up in Windows!
This is based on Wayland and integrates with the Windows window system. Audio works. Clipboard is shared.
Practical uses:
- Run GUI tools like GitKraken or VS Code (Linux versions)
- Browser testing (Firefox Linux version)
- Machine learning visualization tools
WSL's Limitations and Workarounds
WSL 2 isn't perfect. Problems I've encountered:
1. Limited USB Device Access
WSL can't directly access USB devices by default. This is a problem for Arduino or hardware development.
Workaround: The usbipd-win project lets you forward USB to WSL.
2. VPN Issues
Some VPNs (especially corporate ones) don't properly route WSL network traffic. Connecting to VPN can kill WSL's internet.
Workaround: Manually configure DNS or adjust VPN settings.
3. File System Permissions
Windows and Linux have different permission models, so chmod can behave strangely in /mnt/c/.
Workaround: Keep projects inside WSL (~/) and there's no issue.
Looking Back: A Developer Who Returned to Windows
I've been using WSL 2 for over three years now. I own a MacBook, but my main development machine is still a Windows desktop with WSL. Why?
- Value: Same money gets better hardware on Windows than Mac.
- Gaming: I want to play games during breaks.
- Compatibility: Real Linux kernel means identical to server environments.
- Integration: VS Code, Docker, Git integrate perfectly.
WSL 2 isn't just "a tool to run Linux on Windows." It's a technology that changed the development environment paradigm. It's remarkable that Microsoft decided to embrace Linux instead of fighting it, to build "a platform developers want to use."
I wish I could tell my 2015 self: "Wait five years. You'll be able to do amazing development on that Windows laptop."