WSL: The Fastest Way to Get a Real Dev Environment on Windows (No VM Drama)

Was this helpful?

If you’ve ever tried to do “serious” development on Windows, you know the pattern: you start with good intentions, then spend a day installing toolchains, chasing DLL errors, and discovering your build scripts assume bash exists. You can run a full VM, sure—until the fan noise becomes your primary interface and your laptop turns into a portable space heater.

WSL2 is the least dramatic way to get a real Linux environment on a Windows machine, with near-native performance, first-class tooling, and enough escape hatches to keep SREs from developing a nervous tic. But it’s also easy to use wrong. This is the field guide for using it right.

What WSL actually is (and what it isn’t)

WSL is not “Linux apps running in a Windows compatibility layer” (that was the vibe of WSL1). WSL2 is a real Linux kernel running inside a lightweight VM managed by Windows. Your Linux environment is real enough that you can run standard packages, real system calls, real networking stacks, and modern container workflows.

It’s also not a full server VM you should treat like a pet. WSL instances are disposable-ish. You want repeatable configuration, versioned dotfiles, and backups you can actually restore. If your workflow depends on “I configured it once and now I’m afraid to touch it,” you’ve rebuilt the VM drama you came here to avoid.

WSL2 gives you:

  • A Linux filesystem stored in a VHDX (virtual disk) on Windows.
  • Fast Linux-side filesystem operations.
  • Interop: calling Windows binaries from Linux and vice versa.
  • Reasonable integration with editors (especially VS Code Remote).
  • Enough tunables for CPU/memory/swap to stop runaway builds from eating your lunch.

WSL2 also gives you new ways to fail. Mostly around filesystems, networking assumptions, and resource tuning. Conveniently, those are the same areas that bite production systems, so you can treat your workstation like a tiny datacenter and behave accordingly.

Interesting facts and short history you can weaponize

  • WSL1 and WSL2 are fundamentally different beasts. WSL1 translated Linux syscalls to Windows; WSL2 runs a real Linux kernel in a managed VM.
  • WSL2 stores your distro in a VHDX file. That single file is both a blessing (easy backup/export) and a curse (you can fill it with junk fast).
  • Early WSL1 didn’t support key kernel features that container tooling assumes. WSL2 changed the game for Docker and Kubernetes development workflows.
  • Microsoft made systemd support official in WSL after years of “please don’t do init systems here” cultural friction. It’s now a standard option, not a hack.
  • File performance depends on where you keep your code. Linux filesystem operations are fast on the ext4 inside WSL; crossing into /mnt/c is slower and has different semantics.
  • WSLg is a real thing. Linux GUI apps can run integrated on Windows 11 without a DIY X server circus.
  • WSL networking uses NAT and virtual switches. That makes localhost mostly friendly, but “my service is bound to the wrong interface” becomes a frequent workplace mystery.
  • Windows and Linux disagree about case sensitivity and filename rules. If you mix filesystems casually, Git will eventually make it personal.
  • WSL got a Store-delivered versioning model so updates can ship faster than OS releases. This matters when you’re debugging “it works on my Windows build” differences.

Why WSL2 is fast (and when it’s not)

WSL2 feels fast because your Linux tools aren’t emulated; they’re running against a real kernel. Process startup is snappy, the package manager behaves, and typical dev tasks—compiling, running tests, running a local database—are solid.

The fastest thing you can do for WSL performance is boring: keep your repositories in the Linux filesystem (~ or anywhere under /home) and treat Windows mounts (/mnt/c) as “interop-only.” If you ignore this, you’ll spend the rest of your week “optimizing” the wrong thing.

Where it can get slow or weird:

  • Cross-filesystem I/O. Node/npm, Rust, Go, Python virtualenvs, and Git generate lots of small files. Doing that on /mnt/c is a tax you pay per syscall.
  • Antivirus scanning. When Windows Defender scans your build artifacts, it’s like running chaos engineering against your laptop.
  • Memory pressure. WSL2 is a VM. If you let it balloon or swap aggressively, your “fast” dev environment becomes a stuttery horror show.
  • Networking assumptions. Binding to localhost is usually fine, but inbound traffic from other machines requires explicit thought.

One operational truth worth pinning to your monitor: your workstation is a multi-tenant system (Windows + WSL + maybe Docker). Resource isolation is not optional; it’s the difference between “productive” and “why is Teams using 4GB.”

A sane WSL2 setup for real work

Pick a distro like an adult

Use Ubuntu LTS unless you have a strong reason not to. It has the broadest package ecosystem and the fewest “why is this tutorial different” moments. Debian is fine if you prefer it. Alpine is great for containers, not for humans as a daily driver.

Enable systemd (if you want services that behave normally)

If you’re running Docker Engine inside WSL, or you want ssh-agent, postgresql, cron, etc. to behave like they do on real Linux, enable systemd. If you don’t need it, don’t enable it—less moving parts.

Use Windows Terminal and VS Code Remote

Windows Terminal is the right shell UI. VS Code Remote to WSL is the right editing model. Editing Linux files with Windows processes works, but it’s a great way to meet line-ending issues and file watcher quirks you didn’t order.

Resource tuning: set limits before your laptop sets them for you

WSL2 can consume memory opportunistically. That’s great until it isn’t. Put boundaries in place using .wslconfig. You want enough RAM for builds and containers, but you also want Windows to remain responsive.

And now the one quote you should remember when you’re tempted to “just wing it”:

“Hope is not a strategy.” — paraphrased idea often cited in engineering and operations circles

Joke #1: WSL is like a well-run on-call rotation—quiet when you’re doing things correctly, and loud when you get creative.

12+ practical tasks: commands, outputs, decisions

These are the daily-driver checks I use when a WSL setup is being weird. Each task includes: the command, what the output means, and the decision you make from it.

Task 1: Verify you’re on WSL2 (not WSL1)

cr0x@server:~$ wsl.exe -l -v
  NAME            STATE           VERSION
* Ubuntu-22.04    Running         2

Meaning: VERSION 2 confirms you’re on the real kernel-based WSL.

Decision: If you see VERSION 1, convert it (wsl.exe --set-version <distro> 2) before you benchmark anything.

Task 2: Confirm the kernel and environment from inside Linux

cr0x@server:~$ uname -a
Linux cr0x 5.15.133.1-microsoft-standard-WSL2 #1 SMP Wed Oct 11 16:01:22 UTC 2023 x86_64 x86_64 x86_64 GNU/Linux

Meaning: You’re on the Microsoft WSL2 kernel build.

Decision: If you’re missing expected kernel features, update WSL from Windows and reboot WSL.

Task 3: Check disk usage inside the distro (spot silent VHDX growth)

cr0x@server:~$ df -h /
Filesystem      Size  Used Avail Use% Mounted on
/dev/sdc        251G   76G  163G  32% /

Meaning: Your Linux root filesystem has headroom.

Decision: If usage is high, hunt large directories (next task) before you “fix” performance with random tweaks.

Task 4: Find the top space consumers quickly

cr0x@server:~$ sudo du -xhd1 /home | sort -h
4.0K    /home/lost+found
12G     /home/cr0x/.cache
38G     /home/cr0x/projects
50G     /home/cr0x

Meaning: Caches and project directories dominate.

Decision: Clear language/tool caches thoughtfully (pip, npm, cargo) and consider pruning container images if Docker is involved.

Task 5: Measure whether your repo is on Linux FS or Windows mount

cr0x@server:~$ pwd
/home/cr0x/projects/api-service

Meaning: You’re in the Linux filesystem. Good.

Decision: If you see /mnt/c/..., move the repo into /home unless you have a specific interop need.

Task 6: Check mount type and options (diagnose permission weirdness)

cr0x@server:~$ mount | grep -E '/mnt/c|/ '
C: on /mnt/c type drvfs (rw,noatime,uid=1000,gid=1000,metadata,case=off)
none on / type ext4 (rw,relatime,discard,errors=remount-ro)

Meaning: Windows drive is mounted as drvfs; Linux root is ext4.

Decision: If metadata is missing, you’ll have permission/exec-bit pain on /mnt/c. Fix via WSL mount options in /etc/wsl.conf.

Task 7: Verify CPU and memory view (and catch runaway allocation)

cr0x@server:~$ free -h
               total        used        free      shared  buff/cache   available
Mem:           15Gi       6.2Gi       1.9Gi       128Mi       6.9Gi       8.5Gi
Swap:          8.0Gi       0.0Ki       8.0Gi

Meaning: Plenty of available memory; swap not in use.

Decision: If swap is hot, fix memory limits or stop running a database, three browsers, and a container cluster simultaneously.

Task 8: Identify what’s chewing CPU (and whether it’s Windows or Linux)

cr0x@server:~$ top -b -n1 | head -n 12
top - 14:22:01 up  2:11,  0 users,  load average: 2.31, 2.10, 1.88
Tasks:  95 total,   1 running,  94 sleeping,   0 stopped,   0 zombie
%Cpu(s): 12.5 us,  2.1 sy,  0.0 ni, 84.8 id,  0.2 wa,  0.0 hi,  0.4 si,  0.0 st
MiB Mem :  15360.0 total,   2048.0 free,   6360.0 used,   6952.0 buff/cache
PID USER      PR  NI    VIRT    RES    SHR S  %CPU  %MEM     TIME+ COMMAND
812 cr0x      20   0 2539888 312432  40288 R  78.2   2.0   0:34.12 node

Meaning: The Linux process node is hot; CPU wait is low.

Decision: If wa (I/O wait) is high, look at filesystem placement and antivirus. If CPU is hot, it’s probably your build/test workload.

Task 9: Check DNS behavior (common source of “pip/npm is broken”)

cr0x@server:~$ resolvectl status | sed -n '1,30p'
Global
       Protocols: -LLMNR -mDNS -DNSOverTLS DNSSEC=no/unsupported
resolv.conf mode: stub
Current DNS Server: 172.24.112.1
       DNS Servers: 172.24.112.1

Meaning: DNS is coming from a virtual gateway.

Decision: If name resolution fails intermittently, you may need to stop auto-generating /etc/resolv.conf and set stable DNS, especially behind corporate VPN/proxy setups.

Task 10: Confirm localhost ports and listening services

cr0x@server:~$ ss -lntp | head
State  Recv-Q Send-Q Local Address:Port  Peer Address:Port Process
LISTEN 0      4096   127.0.0.1:5432     0.0.0.0:*     users:(("postgres",pid=941,fd=7))
LISTEN 0      4096   0.0.0.0:8080       0.0.0.0:*     users:(("python",pid=1202,fd=3))

Meaning: Postgres is bound to localhost; your app is bound to all interfaces.

Decision: If Windows can’t reach a WSL service, bind to 0.0.0.0 (carefully) or configure port forwarding rules; don’t guess.

Task 11: Validate systemd status (if enabled)

cr0x@server:~$ systemctl is-system-running
running

Meaning: systemd is healthy; services should behave normally.

Decision: If you see degraded, check failing units; don’t ignore it just because “it’s only a dev box.”

Task 12: Check journal logs for service failures

cr0x@server:~$ journalctl -u docker --no-pager -n 10
Feb 04 14:01:12 cr0x systemd[1]: Starting Docker Application Container Engine...
Feb 04 14:01:12 cr0x dockerd[1333]: time="2026-02-04T14:01:12.412" level=info msg="Starting up"
Feb 04 14:01:12 cr0x dockerd[1333]: time="2026-02-04T14:01:12.980" level=info msg="Loading containers: start."
Feb 04 14:01:13 cr0x systemd[1]: Started Docker Application Container Engine.

Meaning: Docker service started cleanly.

Decision: If you see storage-driver errors or permission issues, fix the underlying cause (filesystem location, cgroup settings), not by reinstalling everything.

Task 13: Confirm Git is not silently fighting case sensitivity

cr0x@server:~$ git config --get core.ignorecase
false

Meaning: Git will treat case changes as meaningful, which matches Linux behavior.

Decision: If this is true and your team uses case-sensitive paths, you’re inviting weird diffs and missing files—fix it per-repo.

Task 14: Export a distro for backup (disaster recovery for your dev life)

cr0x@server:~$ wsl.exe --export Ubuntu-22.04 D:\wsl-backups\ubuntu-22.04.tar
Exporting, this may take a few minutes...

Meaning: You created a portable backup archive.

Decision: If you cannot restore your environment quickly, you don’t have a setup—you have a snowflake.

Task 15: Import a distro to a different drive (space and performance management)

cr0x@server:~$ wsl.exe --import Ubuntu-22.04-Work D:\WSL\Ubuntu-Work D:\wsl-backups\ubuntu-22.04.tar --version 2
Importing, this may take a few minutes...

Meaning: You installed a new instance on a chosen path, useful for fast NVMe or larger disks.

Decision: If your system drive is cramped, move the distro. Don’t wait for “disk full” during a release.

Task 16: Sanity-check interop when calling Windows tools

cr0x@server:~$ powershell.exe -NoProfile -Command '$PSVersionTable.PSVersion.ToString()'
7.4.1

Meaning: You can call Windows PowerShell from Linux. Handy for automation glue.

Decision: Use interop deliberately. If your build depends on it, document it, because someone will run it on pure Linux and curse your name.

Storage and filesystem rules that prevent regret

I’m a storage person. I don’t trust “it feels fine” as a metric, and neither should you. WSL2 performance lives and dies by filesystem boundaries.

Rule 1: Keep repos inside the Linux filesystem

Do your work under /home. Put your source code there. Put your dependency caches there. Let Linux do Linux things where Linux semantics exist.

When you develop under /mnt/c, you’re asking for:

  • Slow metadata operations (lots of small file creates/renames).
  • Confusing permission bits (unless you enable metadata support).
  • File watcher weirdness (especially in Node, webpack, and Python reloaders).
  • Case sensitivity mismatches that cause “works for me” bugs.

Rule 2: Treat /mnt/c like an exchange zone

Use it for things that need to be visible to Windows apps: exporting artifacts, dropping logs for a Windows-side viewer, copying data sets, or integrating with corporate tools that only run on Windows. Don’t make it your build workspace.

Rule 3: Plan for VHDX growth and cleanup

Your WSL distro’s disk is a virtual disk image. It grows as you install packages and generate build artifacts. It doesn’t always shrink automatically when you delete files. That’s not a moral failing; it’s how thin-provisioned virtual disks work.

Operational implication: periodically prune caches and container layers. Also, if you run heavy databases locally, consider putting their data in a separate distro or separate storage strategy so your primary dev environment doesn’t become a landfill.

Rule 4: Know what “fast I/O” means for your workload

Compilers, package managers, and test runners often do lots of small random I/O and metadata operations (stat, open, close). That’s where filesystem translation layers hurt. Large sequential reads of a few files might not show it. So when you benchmark, benchmark your actual workload: npm ci, pip install, go test ./..., your real build step.

Networking: localhost, DNS, proxies, and the sharp edges

WSL2 networking is good enough that you forget it exists—until you join a corporate VPN, run a local proxy, or bind to the wrong interface and wonder why nothing can talk to anything.

Localhost: the common happy path

Most dev workflows are “I run a server in WSL and hit it from a browser on Windows.” That usually works because Windows and WSL cooperate for localhost forwarding in common scenarios. But don’t assume it’s magic; verify with ss and actual requests.

Bind addresses: choose intentionally

Binding to 127.0.0.1 means “only inside WSL.” Binding to 0.0.0.0 means “all interfaces,” which makes it reachable from Windows and potentially other networks depending on firewall and routing.

If you’re working with sensitive services (databases, internal admin UIs), keep them on localhost unless you have a clear need. Developers accidentally exposing a debug port is how you end up in a security training slideshow.

DNS: when “it’s intermittent” is your clue

Corporate VPN clients often install their own DNS and routing policies. WSL may pick up a stub resolver address that sometimes works and sometimes doesn’t, depending on whether the VPN is in a good mood. When you see package installs failing with name resolution errors, treat DNS as a first-class suspect.

Proxies: don’t half-configure them

If your network requires an HTTP proxy, configure it consistently across:

  • Environment variables (HTTP_PROXY, HTTPS_PROXY, NO_PROXY)
  • Git proxy settings if needed
  • Language-specific tooling (npm, pip, cargo) if needed
  • Docker daemon / build configuration if you build images

Half-configured proxies create “works sometimes” failures that waste the most expensive resource in engineering: your attention.

Docker and containers on WSL2 without foot-guns

There are two common patterns:

  1. Docker Desktop using the WSL2 backend. Usually the easiest. Windows manages the integration, and your Linux tools talk to Docker seamlessly.
  2. Docker Engine installed inside the WSL distro. Works well if you want a more Linux-native experience, especially with systemd, but you own more of the plumbing.

Storage for containers: the invisible disk hog

Container layers accumulate. They always do. If you don’t prune, your VHDX grows, performance degrades, and you’ll eventually hit “no space left on device” mid-build.

Filesystem placement still matters

Build contexts living on /mnt/c can make Docker builds painfully slow because sending build context and reading metadata crosses the boundary. Keep build contexts in the Linux filesystem.

Joke #2: Docker image layers are like office snacks—nobody admits they’re responsible, but they disappear slowly until the budget meeting.

Three corporate-world mini-stories (pain included)

Mini-story 1: The incident caused by a wrong assumption

A mid-sized company rolled out a “standard dev environment” policy: Windows laptops, WSL2 for backend services, a shared monorepo, and a local Postgres container. Sounds modern. It worked fine for weeks—until a release week when a few developers started reporting that migrations “randomly fail” and the API “sometimes can’t connect to the database.”

The wrong assumption was subtle: they assumed localhost meant the same thing everywhere, and that if a service listened on localhost inside WSL, it was automatically reachable from Windows apps. Some developers ran the API in WSL and the DB in Docker Desktop; others ran both inside WSL; a few ran the API in Windows and the DB in WSL. The connection string localhost:5432 was treated like a universal truth.

In practice, the networking path depended on where the client lived. A Windows process hitting localhost was not always talking to WSL’s localhost. Some setups forwarded correctly; others didn’t, especially when the VPN client toggled routes and the Hyper-V virtual switch reset.

The fix wasn’t a magical registry tweak. They standardized the topology: API and DB run in the same environment (both in WSL, or both in Docker Desktop with WSL backend). They wrote two canonical connection strings and put them in tooling that detected the runtime context. They also added a simple health check script that validated connectivity from the actual client process, not from “where we think it runs.”

After that, the “randomness” vanished. It wasn’t random; it was unstated architecture.

Mini-story 2: The optimization that backfired

A different org had a performance problem: npm install was slow. Someone decided to “optimize” by keeping the repo on the Windows filesystem so Windows indexing and enterprise backup tools could see it. They moved the monorepo under C:\dev and accessed it from WSL via /mnt/c/dev.

At first it looked fine. Builds started, tests ran, and the team congratulated itself for being pragmatic. Two weeks later, the bug reports started: hot reload missing changes, Jest watchers spiking CPU, Git reporting modified files that nobody touched, and TypeScript builds occasionally failing because a file “does not exist” for a millisecond.

They’d optimized for the wrong observer. The Windows filesystem path introduced metadata latency and semantic mismatches, especially for tools that watch file events and expect Linux behavior. The overhead wasn’t just speed—it was correctness. Developers wasted hours “fixing” their code when the filesystem was the one lying to them.

The actual optimization was the opposite: keep repos in the Linux filesystem for correctness and speed, then selectively sync artifacts back to Windows when needed. They also excluded the WSL VHDX location from aggressive scanning policies where possible. The end result: faster installs and fewer phantom file issues.

Performance wins that break correctness are not wins. They’re debt with interest.

Mini-story 3: The boring but correct practice that saved the day

A large enterprise team treated WSL dev environments the way SREs treat production: as disposable, reproducible units. Every developer had a bootstrap script that installed packages, configured shell tooling, pinned language runtimes, and set up SSH keys and known hosts policies. It wasn’t glamorous; it was a pile of scripts and a checklist.

One Monday, a Windows update caused a subset of machines to behave oddly with virtualization features. WSL distros wouldn’t start reliably; some got stuck “Running” but didn’t respond. The usual outcome for teams is lost days and a lot of personal debugging rituals.

This team’s response was almost boring: export the distro if possible, then rebuild from scratch on affected machines using the script. If export failed, they restored from a recent export archive. Developers were back online the same day, and the team lead didn’t have to beg IT for miracles.

The practice that saved them was simple: periodic WSL exports + reproducible setup. It turned a workstation incident into a minor inconvenience.

In ops terms, they had an RTO for developer productivity. Most teams don’t. They should.

Fast diagnosis playbook: what to check first/second/third

When WSL “feels slow” or “is acting weird,” you want to identify the bottleneck in minutes, not hours. Here’s the triage order that works in practice.

First: Confirm where the work is happening (filesystem boundary)

  1. Run pwd. If you’re under /mnt/c, assume that’s the culprit until proven otherwise.
  2. Run mount | grep /mnt/c and check if metadata is enabled if you rely on permissions/exec bits.
  3. Move the repo to /home and rerun the slow command. If it’s suddenly fine, stop diagnosing and adopt the fix.

Second: Check resource pressure (memory and swap)

  1. Run free -h. If available memory is low and swap is used, expect stutters.
  2. Run top. If load average is high with low CPU usage, it may be I/O wait or contention.
  3. Decide: set sane WSL limits, reduce container footprint, or stop running heavyweight services locally.

Third: Check antivirus and indexing impacts (Windows side)

  1. If your repo is on Windows filesystem, expect scanning overhead—move it.
  2. If your distro’s VHDX location is being scanned aggressively, you’ll see intermittent spikes during builds.
  3. Decision: coordinate with corporate endpoint policy if allowed; otherwise design your workflow to minimize the scan surface.

Fourth: Networking and DNS (especially behind VPN)

  1. Run resolvectl status and validate DNS server presence.
  2. Try name resolution (getent hosts for a known domain).
  3. Decision: pin DNS config, adjust VPN split-tunnel settings if permitted, and ensure proxy variables are consistent.

Fifth: Version drift (WSL, kernel, distro packages)

  1. Run wsl.exe --status on Windows and uname -a in Linux.
  2. Update WSL and the distro packages if you’re chasing a known bug.
  3. Decision: standardize versions across the team; don’t debug “works on my laptop” kernel mismatches forever.

Common mistakes: symptom → root cause → fix

1) Symptom: Git status is slow, npm installs take forever

Root cause: Repo is on /mnt/c (drvfs). Heavy metadata workloads are penalized.

Fix: Move repo into /home. Keep Windows access via selective sync (copy artifacts out), not by developing on drvfs.

2) Symptom: Permission denied / scripts not executable under /mnt/c

Root cause: drvfs mounted without metadata, so Linux permission bits aren’t preserved.

Fix: Configure /etc/wsl.conf to mount with metadata, or keep executable scripts inside the Linux filesystem where permissions behave.

3) Symptom: “Temporary failure resolving …” during package installs

Root cause: DNS forwarding instability, often triggered by VPN clients or changing network profiles.

Fix: Disable auto-generation of /etc/resolv.conf and set stable DNS, or adjust the VPN/proxy policy to provide consistent resolver behavior.

4) Symptom: Service runs in WSL but browser on Windows can’t reach it

Root cause: Service bound to 127.0.0.1 inside WSL or port forwarding not active.

Fix: Bind to 0.0.0.0 for dev where appropriate, verify with ss -lntp, and use explicit port forwarding if required by your setup.

5) Symptom: Docker builds are slow; context send takes ages

Root cause: Build context on Windows filesystem; lots of file metadata traversals cross boundaries.

Fix: Keep Dockerfile and context in Linux filesystem. Prune images and builder cache routinely.

6) Symptom: WSL memory usage keeps growing and Windows feels sluggish

Root cause: WSL2 VM opportunistically uses RAM; memory isn’t reclaimed quickly under some workloads.

Fix: Set limits in .wslconfig, restart WSL when needed, and avoid running container stacks you don’t need daily.

7) Symptom: File watchers miss changes or spin CPU

Root cause: Editing Linux files with Windows tooling across filesystem boundaries, or watching files on drvfs.

Fix: Use VS Code Remote to WSL; keep watched files in Linux FS; reduce watcher scope; use polling only as a last resort.

8) Symptom: “No space left on device” even after deleting stuff

Root cause: VHDX grew; deleting files doesn’t always shrink the virtual disk immediately.

Fix: Prune caches, export/import to compact logically, and keep heavy data in a planned location. Don’t wait for a crisis.

Checklists / step-by-step plan

Checklist A: 30-minute setup that won’t embarrass you later

  1. Install WSL2 and choose Ubuntu LTS.
  2. Update packages inside the distro; install core tools (git, build-essential, curl, ca-certificates).
  3. Decide on systemd: enable only if you need long-running services.
  4. Create ~/projects and keep repos there.
  5. Use Windows Terminal as your shell UI.
  6. Use VS Code Remote to WSL for editing and debugging.
  7. Configure SSH keys and agent strategy (Windows agent forwarding or Linux agent, but pick one).
  8. Set .wslconfig memory/CPU/swap boundaries appropriate to your machine.
  9. Run the “12+ tasks” checks once to baseline behavior.
  10. Export the distro once it’s clean. This is your golden image.

Checklist B: Daily operating habits (the ones that prevent slow creep)

  1. Keep build caches under control (prune old virtualenvs, node_modules in dead branches, stale container layers).
  2. Don’t run heavyweight services you don’t need today.
  3. Prefer Linux-native tools inside WSL; use interop sparingly.
  4. When something breaks after a VPN change, check DNS before reinstalling toolchains.
  5. Export your distro periodically if your work depends on local state.

Checklist C: When performance tanks, do this in order

  1. Confirm repo location: if it’s on /mnt/c, move it.
  2. Check memory/swap: if swapping, set limits and reduce workload.
  3. Check I/O wait and top CPU offenders.
  4. Check DNS and proxy settings if network operations are slow.
  5. Only then consider WSL updates, resets, or a distro rebuild.

FAQ

Is WSL2 “just a VM”?

Technically yes: a lightweight VM with a real Linux kernel. Practically, it behaves like an integrated subsystem with excellent interop and tooling support.

Should I use WSL1 for anything?

Rarely. WSL1 can be useful for specific filesystem access patterns on Windows drives, but for modern Linux tooling and containers, WSL2 is the default.

Where should I put my source code?

Inside the Linux filesystem, under /home. Use /mnt/c for exchange with Windows tools, not for builds and dependency installs.

Can I run Docker without Docker Desktop?

Yes, by installing Docker Engine inside the WSL distro. It’s viable, especially with systemd enabled, but you’ll own more configuration and updates.

Why does my WSL disk usage not shrink after deleting files?

Because the virtual disk grew to accommodate data, and thin-provisioned disks don’t always compact automatically. Plan cleanup and consider export/import as a practical compaction approach.

How do I make Windows reach a service running in WSL?

Ensure the service is listening on an appropriate interface (0.0.0.0 if needed) and verify with ss -lntp. Then test connectivity from Windows. Don’t rely on assumptions about localhost forwarding.

Do I need systemd in WSL?

Only if you want Linux services to run and be managed normally (Docker Engine, databases, background daemons). If you’re just running CLI tools and ephemeral processes, you can skip it.

What’s the safest backup strategy for WSL?

Use wsl.exe --export to create periodic archives, plus keep your configuration in version control (dotfiles, bootstrap scripts). Backups without restore practice are theoretical.

Why do file watchers behave oddly?

Often because the files live on drvfs (/mnt/c) or are edited across boundaries. Keep watched project files in Linux FS and use WSL-native editing (VS Code Remote).

Can I run GUI Linux apps?

On Windows 11 with WSLg, yes, and it’s surprisingly usable. For production-grade Linux GUI work, you still want a real Linux desktop or remote environment, but for dev tooling it’s fine.

Next steps that actually improve your life

If you want the shortest path to a “real dev environment” on Windows, do these things and stop improvising:

  1. Move your repos into the Linux filesystem and treat /mnt/c as a staging area.
  2. Baseline your system using the tasks above: verify WSL2, check mounts, confirm memory behavior, validate DNS, and check listening ports.
  3. Set WSL resource limits so your dev environment can’t DOS your desktop.
  4. Pick one container strategy (Docker Desktop WSL backend or Docker in-distro) and standardize it for your team.
  5. Make it recoverable: export the distro once you’ve got it right, and keep a reproducible bootstrap script so a rebuild is a routine event, not a catastrophe.

WSL2 is not a science project. It’s infrastructure. Treat it like infrastructure and it will behave like it. Treat it like a weekend hobby and it will eventually do what all weekend hobbies do: break right before Monday.

← Previous
PowerShell vs CMD: When CMD Still Wins (and When It Doesn’t)
Next →
Linux Firewall: The Clean nftables Layout That Stays Readable at 500 Rules

Leave a comment