You installed WSL because everyone said it’s “basically Linux on Windows now.” Then you tried to run something slightly non-demo—maybe a VPN client, a packet capture, a Kubernetes lab, or a service that expects to be PID 1—and suddenly you’re troubleshooting ghosts. The app runs, but the behavior isn’t quite Linux. Or it’s Linux, but only the parts that fit into a developer convenience layer.
WSL is fantastic. It’s also not a virtual machine in the way your production instincts assume. VirtualBox is older, heavier, and sometimes annoying. It’s also a real VM with sharp edges you can reason about. If you’re running serious workloads—or you want reliability more than vibes—you need to know where the boundary is.
Draw the decision line: what you’re really choosing
People frame this as “WSL vs VirtualBox.” That’s not the actual choice. The choice is:
- Convenience sandbox vs controllable system. WSL is a convenience sandbox tightly integrated with Windows. VirtualBox gives you a controllable system boundary.
- Windows-first integration vs Linux-first behavior. WSL optimizes the developer loop on Windows. VirtualBox optimizes “this behaves like Linux on hardware,” because that’s what it is.
- Shared fate vs explicit fate. WSL shares more fate with the Windows host: file I/O paths, network intermediaries, memory pressure behavior, update cadence, corporate endpoint tooling interference. VirtualBox is still affected by the host, but your blast radius is easier to reason about.
If your workload requires kernel control, low-level networking, predictable storage semantics, or the ability to treat the guest as a separate machine: default to a VM. If your workload is mostly userland tooling and you care about Windows integration: default to WSL.
Pragmatic rule: if you plan to file bugs that begin with “On Linux this works,” you probably want VirtualBox. If your plan is “I just need a shell, git, and a compiler,” you probably want WSL.
Facts and historical context (the parts people forget)
- WSL 1 wasn’t a VM. It translated Linux syscalls into Windows NT calls. That made it clever, fast in some cases, and fundamentally incompatible in others.
- WSL 2 switched to a real Linux kernel. That’s the big turning point: WSL 2 runs a Microsoft-provided Linux kernel in a lightweight VM using Hyper-V components.
- VirtualBox predates WSL by a decade. VirtualBox started at Innotek, then Sun, then Oracle. It’s old enough to have solved boring problems like “boot a guest with predictable devices.”
- Hyper-V and VirtualBox have a history. For years they competed for hardware virtualization features. Modern VirtualBox can run on hosts with Hyper-V enabled, but performance and feature interactions can still be… spicy.
- WSL’s filesystem model is intentionally split. Linux files stored inside WSL’s virtual disk behave differently than Windows files mounted under
/mnt/c. That difference is not cosmetic; it’s a performance and correctness line. - Corporate security tooling changed the game. Endpoint protection and DLP agents inspect file operations and network traffic. WSL’s integration paths can amplify that overhead in ways a VM disk image doesn’t.
- VirtualBox became the default dev VM substrate for a generation. Tools like Vagrant popularized “one VM per project.” That expectation maps cleanly to VirtualBox and less cleanly to WSL.
- WSL gained systemd support later. Early WSL users had to duct-tape init behavior. Modern WSL can run systemd, but that doesn’t magically turn WSL into “a server.”
- Networking expectations diverged. VirtualBox gives you classic NIC modes (NAT, bridged, host-only). WSL 2 uses a NATed virtual network with Windows as an intermediary, which affects inbound connectivity and packet visibility.
Architecture differences that actually matter
WSL 2: a lightweight VM that doesn’t want to feel like one
WSL 2 runs a Linux kernel inside a managed VM. Microsoft did the hard work so you don’t have to think about BIOS settings, disk controllers, and boot sequences. That’s the appeal. But the same “managed” approach means you don’t get to control a lot of knobs that matter when you’re diagnosing performance, networking, or storage behavior.
The headline differences you will feel:
- Filesystem boundary: Linux ext4-in-a-vhdx vs Windows NTFS mounted into Linux under
/mnt. Different performance characteristics; different file watcher behavior; different metadata semantics under load. - Networking boundary: WSL 2 is behind NAT by default, with Windows doing routing and port forwarding. Inbound connections, multicast, and “just bind to 0.0.0.0” expectations can get weird.
- Resource governance: WSL’s VM memory grows on demand and doesn’t always shrink when you think it should unless you tune it. It’s convenient until it’s not.
- Kernel/module expectations: You run Microsoft’s kernel. You can’t freely load arbitrary kernel modules like you would on a normal distro install.
VirtualBox: a conventional VM with conventional tradeoffs
VirtualBox is “a computer in a window.” You get explicit virtual hardware and more predictable guest behavior. You also get overhead, device emulation limitations, and the daily chores of VM lifecycle: updates, snapshots, disk sizing, and networking modes.
But when something breaks, VirtualBox is the kind of broken you can reason about: NIC mode misconfigured, disk I/O throttled, host-only interface not created, guest additions missing. These are boring failures. Boring failures are survivable.
One paraphrased idea worth keeping on a sticky note, attributed to Werner Vogels: Everything fails; build systems that assume failure and recover quickly.
(paraphrased idea)
First joke, because we’re going to talk about networking: NAT is like office small talk—fine for outbound, awkward when someone tries to initiate a real conversation.
When WSL is the right tool
WSL shines when your goal is to be productive on Windows without maintaining a full VM. It’s a developer platform, not a miniature datacenter.
Use WSL when you want fast iteration and tight Windows integration
- CLI dev tooling: git, ssh, curl, language runtimes, package managers, build systems.
- Text-based workflows: editors, linters, compilers, unit tests, local scripts.
- Containers (often): Docker Desktop uses WSL 2 as a backend for Linux containers; for many dev workflows this is the easiest path.
- Cross-platform dev: You can build Linux-target artifacts from Windows without dual-booting.
- Low-risk local services: Running a dev database or cache can be fine, as long as you don’t treat it like a production baseline.
WSL’s “good weird” features
- Coherent Windows access: You can call Windows executables from WSL and vice versa. That’s not “Linux-like,” but it’s extremely useful.
- Frictionless install: You can provision a distro in minutes without an ISO or a VM manager.
- Lower operational load: No guest OS lifecycle to babysit in the same way as a full VM; fewer moving parts to patch manually.
WSL is a great default for local development as long as you keep your Linux project files inside the WSL filesystem (not on /mnt/c) and you accept that some “real Linux” assumptions won’t hold.
When WSL is the wrong tool (and VirtualBox wins)
If you need “a machine,” not “a shell,” WSL starts to show its seams. Here are the decision-changing cases.
1) You need kernel-level behavior, modules, or low-level observability
If you’re doing eBPF work, kernel module development, custom iptables/nftables flows that expect full control, or anything that needs a specific kernel config: use a real VM. WSL’s kernel is real, but it’s not yours. You don’t get full parity with a distro kernel and you can’t assume you can load what you want.
2) You need predictable inbound networking, L2 behavior, or “real host” semantics
VirtualBox bridged mode gives the guest an address on your LAN. That’s still virtualized, but it aligns with “this is another machine.” WSL 2’s NAT model is fine for outbound connections and localhost dev. It’s less fine when you need inbound access from other devices, multicast discovery, lab environments, or packet captures that must see the truth.
3) You need to test boot, init, or full OS lifecycle behaviors
Testing systemd units, boot ordering, cloud-init, disk encryption at boot, or kernel command line changes? Use VirtualBox. WSL has gotten better (including systemd support), but the lifecycle is still “WSL instance as a managed environment,” not “a server you can treat like cattle.”
4) Your workload is storage-sensitive or correctness-sensitive
WSL is fast when you operate on files inside its Linux filesystem. It can be painfully slow when you run heavy file churn on Windows-mounted paths. The slow path is not a bug; it’s a boundary. If your workload requires large repo checkouts, massive node_modules trees, or database workloads that need predictable fsync semantics, you need to plan around that boundary or move to a VM.
5) You need USB passthrough or hardware access
VirtualBox has mature USB passthrough support (with extension pack) and a fairly classic story for attaching devices to a guest. WSL has some options for USB/IP scenarios, but it’s not the same. If you’re flashing devices, talking to dongles, using serial adapters, or doing embedded work: VirtualBox is usually the clean answer.
6) You need strong isolation from corporate endpoint tooling
Here’s an uncomfortable truth: security tooling often treats WSL as “just more Windows file access.” That can mean extra scanning overhead when you build or unpack lots of small files on /mnt/c. A VirtualBox disk image can be more insulated because the host sees mostly big sequential reads/writes to a single file, not a storm of file operations.
7) You need stable reproducibility for a team
WSL is reproducible enough for many dev teams, but it’s still tied to Windows versions, WSL versions, and sometimes subtle integration behavior. VirtualBox VMs can be pinned more explicitly: a known ISO, known guest additions version, known virtual hardware, known network mode. That matters when “works on my machine” is costing real money.
Second joke, because storage always deserves one: “It worked until we put the repo on /mnt/c” is the modern version of “it worked on my laptop,” just with more syscalls.
Practical tasks: commands, outputs, and decisions
You don’t pick tools by vibes. You pick them by measuring the bottleneck and checking the assumptions that usually betray you: filesystem path, network mode, memory pressure, and host/guest boundaries. Below are practical, runnable commands with example outputs and what you should decide from them.
Task 1: Confirm WSL version and distro state
cr0x@server:~$ wsl.exe --list --verbose
NAME STATE VERSION
* Ubuntu-22.04 Running 2
Debian Stopped 2
What it means: You’re on WSL 2 (kernel VM). If you expected WSL 1 semantics (syscall translation), stop. Your troubleshooting path differs.
Decision: If your problem is kernel/module/network-stack related, treat WSL like a managed VM with constraints. Don’t assume you can “just tweak the kernel.”
Task 2: Identify where your project files live (WSL ext4 vs Windows mount)
cr0x@server:~$ pwd
/mnt/c/Users/alex/work/myapp
What it means: You’re building on the Windows filesystem via DrvFs. For heavy file churn, this is the slow lane.
Decision: If builds/tests are slow, move the repo into the WSL filesystem (e.g., /home/alex) before blaming compilers, Node, or “WSL being slow.”
Task 3: Measure filesystem type and mount options inside WSL
cr0x@server:~$ mount | head -n 6
none on / type lxfs (rw,noatime)
none on /mnt/wsl type tmpfs (rw,nosuid,nodev,noexec,relatime)
C:\ on /mnt/c type 9p (rw,noatime,aname=drvfs;path=C:\;uid=1000;gid=1000;metadata;case=off)
proc on /proc type proc (rw,nosuid,nodev,noexec,relatime)
tmpfs on /run type tmpfs (rw,nosuid,nodev,size=1632048k,mode=755)
What it means: The Windows drive is mounted via a 9p-based mechanism. That’s the boundary that can crush small-file workloads.
Decision: If your workload is sensitive to metadata ops (git status, npm install, yarn, pip, composer), keep it off /mnt/c.
Task 4: Quick small-file stress test (diagnose the boundary)
cr0x@server:~$ time bash -lc 'rm -rf /tmp/iotest && mkdir -p /tmp/iotest && for i in $(seq 1 20000); do echo $i > /tmp/iotest/f$i; done'
real 0m2.941s
user 0m0.715s
sys 0m2.147s
What it means: This is your baseline inside the Linux filesystem.
Decision: Run the same test under /mnt/c. If it’s dramatically slower, your “WSL performance issue” is actually a path issue.
Task 5: Repeat the stress test on /mnt/c and compare
cr0x@server:~$ time bash -lc 'rm -rf /mnt/c/Temp/iotest && mkdir -p /mnt/c/Temp/iotest && for i in $(seq 1 20000); do echo $i > /mnt/c/Temp/iotest/f$i; done'
real 0m38.772s
user 0m2.105s
sys 0m15.883s
What it means: You’re paying cross-boundary overhead per file. Multiply that by package managers and it gets ugly.
Decision: Move workloads into WSL’s ext4 or use a VM with a native Linux filesystem for that workload. Don’t “optimize” your compiler flags to fix a filesystem boundary.
Task 6: Check memory pressure and whether WSL is hoarding RAM
cr0x@server:~$ free -h
total used free shared buff/cache available
Mem: 7.7Gi 6.1Gi 431Mi 152Mi 1.2Gi 1.1Gi
Swap: 2.0Gi 1.4Gi 612Mi
What it means: WSL VM is under pressure and swapping. That often feels like “everything is slow,” including I/O and networking.
Decision: If you need sustained memory-heavy workloads, consider tuning WSL resource limits or move the workload to a VM with explicit RAM sizing. Swapping in dev is the sound of your afternoon being stolen.
Task 7: Check WSL kernel and feature expectations (systemd, modules)
cr0x@server:~$ uname -a
Linux cr0x 5.15.133.1-microsoft-standard-WSL2 #1 SMP Wed Oct 11 19:10:13 UTC 2023 x86_64 GNU/Linux
What it means: You’re running the WSL kernel flavor. It’s legitimate, but it’s not your distro’s exact kernel build.
Decision: If you require a specific kernel version/config (e.g., for eBPF features, LSM behavior, or module availability), don’t gamble. Use VirtualBox with a distro kernel you control.
Task 8: Diagnose inbound reachability (WSL NAT reality check)
cr0x@server:~$ ip addr show eth0 | sed -n '1,8p'
2: eth0: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc mq state UP group default qlen 1000
link/ether 00:15:5d:3a:7c:11 brd ff:ff:ff:ff:ff:ff
inet 172.28.96.15/20 brd 172.28.111.255 scope global eth0
valid_lft forever preferred_lft forever
What it means: Your WSL instance is on a private NAT network (172.28.x.x). Other LAN devices won’t reach it directly.
Decision: If you need other machines to connect to services inside Linux without port-proxy gymnastics, prefer VirtualBox bridged networking.
Task 9: Check what Windows thinks your VirtualBox VM networking mode is (guest-side clue)
cr0x@server:~$ ip route
default via 10.0.2.2 dev enp0s3 proto dhcp src 10.0.2.15 metric 100
10.0.2.0/24 dev enp0s3 proto kernel scope link src 10.0.2.15
What it means: That 10.0.2.2 gateway is classic VirtualBox NAT.
Decision: If you need inbound access from your LAN, NAT is the wrong mode. Switch to bridged or host-only + explicit routing depending on your needs.
Task 10: Validate bridged networking on a VirtualBox guest
cr0x@server:~$ ip addr show enp0s3 | sed -n '1,8p'
2: enp0s3: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc fq_codel state UP group default qlen 1000
link/ether 08:00:27:aa:bb:cc brd ff:ff:ff:ff:ff:ff
inet 192.168.1.77/24 brd 192.168.1.255 scope global dynamic noprefixroute enp0s3
valid_lft 7200sec preferred_lft 7200sec
What it means: The VM is on the same LAN as your host. Other devices can reach it, and service discovery behaves normally.
Decision: For labs, multi-node testing, or inbound services, this is often the cleanest setup.
Task 11: Check disk usage and the “WSL vhdx never shrinks” surprise
cr0x@server:~$ df -h /
Filesystem Size Used Avail Use% Mounted on
/dev/sdc 1007G 96G 861G 11% /
What it means: Inside WSL, you may have plenty of free space. On Windows, the underlying vhdx may still be huge after deletes.
Decision: If your Windows disk is filling up, you must reclaim space at the virtual disk level (export/import or compact). If you need predictable disk growth behavior, a VM with explicit virtual disk management can be easier to operate.
Task 12: Confirm whether systemd is actually PID 1 in WSL
cr0x@server:~$ ps -p 1 -o comm=
systemd
What it means: systemd is running as init. If it’s init or something else, your service management expectations may be wrong.
Decision: If your stack relies on systemd units and timers behaving like a server, ensure systemd is enabled; otherwise use a VM and stop bargaining with init.
Task 13: Verify you can bind and reach a service from Windows host (WSL localhost integration)
cr0x@server:~$ python3 -m http.server 8080
Serving HTTP on 0.0.0.0 port 8080 (http://0.0.0.0:8080/) ...
What it means: Service is listening. Typically Windows can reach it via localhost:8080 thanks to WSL integration.
Decision: If Windows can reach it but other LAN devices cannot, that’s WSL NAT by design. Decide whether you need LAN reachability; if yes, consider a bridged VM.
Task 14: Diagnose DNS problems (WSL vs VM behaves differently under VPNs)
cr0x@server:~$ resolvectl status | sed -n '1,18p'
Global
Protocols: -LLMNR -mDNS -DNSOverTLS DNSSEC=no/unsupported
resolv.conf mode: stub
Link 2 (eth0)
Current Scopes: DNS
Protocols: +DefaultRoute
Current DNS Server: 172.28.96.1
DNS Servers: 172.28.96.1
What it means: DNS is being mediated. Under some corporate VPN clients, this can break split-DNS expectations or cause intermittent resolution failures.
Decision: If DNS is flaky and you need deterministic networking for a lab or CI-like behavior, a VM with its own NIC on bridged mode often sidesteps the weirdness.
Fast diagnosis playbook
When someone says “WSL is slow” or “VirtualBox networking is broken,” you have about five minutes to avoid a day of superstition. Here’s a tight order of operations that finds the bottleneck quickly.
First: identify the boundary you’re crossing
- Where are the files? If the project is under
/mnt/c, assume filesystem boundary overhead first. - Where is the network? If the guest IP is private NAT space, assume inbound reachability issues first.
- Where is the CPU? If the host is under load or the VM is starved, assume scheduling and memory pressure.
Second: run the three “truth serum” checks
- Filesystem check: Run the small-file stress test in both locations. Big delta = boundary problem, not “your app.”
- Memory check: Look for swap and low available memory. Swap = everything lies.
- Network check: Confirm IP address type and route. NAT vs bridged tells you what’s possible without hacks.
Third: decide whether you’re debugging the app or the platform
- If the failure is caused by kernel/module assumptions, packet visibility, inbound networking, or storage semantics: move to VirtualBox (or another full hypervisor).
- If the issue is path placement, resource tuning, or a missing integration setting: fix WSL and keep the dev loop tight.
Quick “bottleneck map”
- Slow builds/tests: usually filesystem path or endpoint scanning overhead.
- Can’t reach service from another machine: WSL NAT; VirtualBox NAT; wrong mode.
- VPN + DNS weirdness: mediated DNS; decide if you need a real NIC.
- Clock/time drift: VM host time sync issues (more common in VMs than WSL).
- “Works on Linux, not here”: kernel/module/privilege mismatch; choose a VM.
Common mistakes: symptoms → root cause → fix
1) Symptom: npm/yarn/pip installs are glacial in WSL
Root cause: Repo lives on /mnt/c and the workload does massive small-file operations across the Windows↔Linux boundary.
Fix: Move the repo to /home/<user> inside WSL. If you must keep sources on Windows, consider a VM with shared folders only for lightweight edits, not dependency trees.
2) Symptom: a service binds to 0.0.0.0 but other LAN devices can’t reach it (WSL)
Root cause: WSL 2 is behind NAT. Windows can often reach it via localhost integration; your LAN can’t.
Fix: Use port proxying/forwarding if acceptable, or move the service into a bridged VM (VirtualBox) for real LAN presence.
3) Symptom: packet capture doesn’t show expected traffic
Root cause: WSL’s virtual networking path and Windows mediation hide/transform what you think you’re capturing; you may be capturing the wrong interface layer.
Fix: Use a VM with bridged networking and capture on the guest NIC, or capture on the Windows host at the correct interface. If you need L2 truth, prefer a VM.
4) Symptom: disk space on Windows disappears after “deleting lots of stuff” in WSL
Root cause: The WSL virtual disk grows but doesn’t automatically shrink; deletes inside ext4 don’t immediately compact the VHDX.
Fix: Reclaim space by compacting/export-import the distro. If this happens repeatedly, set expectations: WSL is not a thin-provisioned storage manager you can ignore.
5) Symptom: “Hyper-V is enabled so VirtualBox is slow/weird”
Root cause: VirtualBox may run atop Hyper-V APIs depending on host configuration. Performance and timing behavior can change, and some features can be impacted.
Fix: Decide which hypervisor is the primary. If you must keep Hyper-V features, test VirtualBox performance on your baseline workloads. If you need maximum VM performance, consider running without competing hypervisor layers.
6) Symptom: system services don’t start reliably in WSL
Root cause: systemd not enabled or lifecycle is not equivalent to a full booted machine; background services may depend on init behavior.
Fix: Enable systemd for the distro if appropriate and verify PID 1. If service lifecycle is core to what you’re testing, stop fighting it and use a VM.
7) Symptom: file watchers miss changes or trigger storms
Root cause: Cross-filesystem watcher semantics differ; inotify behavior across mounted Windows paths is not equivalent to native Linux FS behavior.
Fix: Keep watched trees inside the WSL filesystem. For large monorepos with heavy watch usage, a VM may behave more predictably.
8) Symptom: “My database is slower in WSL than expected”
Root cause: Database files placed on /mnt/c, or host memory pressure causing swap, or antivirus scanning the working set.
Fix: Put the data directory inside WSL ext4, allocate more memory, exclude appropriate paths from scanning per corporate policy, or move DB workloads into a VM.
Three corporate mini-stories from the trenches
Mini-story 1: The incident caused by a wrong assumption
A mid-sized SaaS company (call them “Northbridge”) standardized local dev on Windows laptops. The platform team blessed WSL 2 as the default and moved on. It was a good call for day-to-day coding.
Then a team building a network-heavy agent started testing “realistic” behavior inside WSL. Their agent listened for inbound connections from other machines on the corporate LAN. Developers reported intermittent connectivity: sometimes it worked from the Windows host, sometimes it didn’t from a second laptop. They blamed firewall rules, then blamed the agent, then blamed the QA lab switches, because that’s what humans do under time pressure.
The wrong assumption: “Binding to 0.0.0.0 inside WSL is equivalent to binding on a host NIC.” It isn’t. WSL 2 lives behind NAT, and Windows does clever localhost forwarding that makes things appear fine from the same machine. From the LAN, it’s a different story.
The “incident” wasn’t an outage; it was worse: a false confidence period. They merged a change that looked correct in WSL testing but failed in a staging lab running real Linux hosts. Two days of debugging later, the root cause was simply that their test platform never matched their deployment topology.
The fix was boring and immediate: they moved network integration tests into VirtualBox VMs running bridged networking. WSL stayed for coding. The team stopped trying to make WSL behave like a routable host. Productivity went up, and the argument count went down.
Mini-story 2: The optimization that backfired
Another company (“Red Quarry”) ran a large monorepo with a heavy Node toolchain. Someone decided to “optimize” by keeping the repo on the Windows filesystem so Windows-native tools could index it and backups would be simpler. The repo was mounted into WSL under /mnt/c. Everyone was told this was the modern, integrated way.
At first it was fine. Then dependency count grew. Build steps started creating and deleting hundreds of thousands of small files. Developers quietly began running builds overnight. That’s not a workflow; that’s surrender.
The optimization backfired because it targeted the wrong bottleneck. The team optimized for “single copy of files” and “Windows tooling convenience,” but paid for it with per-file overhead and endpoint scanning amplification. Every file create became a cross-boundary operation plus security inspection. The machine was doing work; just not the work anyone asked for.
Eventually, the SRE on the team ran a tiny stress test: create 20k files in /tmp vs /mnt/c. The ratio was humiliating. They moved repos into the WSL filesystem by default and used editor integrations to access them. Indexing was reconfigured to target Linux-side paths where possible.
The result: build times dropped dramatically without touching a single compiler flag. The “optimization” they needed was not faster code—it was fewer boundary crossings.
Mini-story 3: The boring but correct practice that saved the day
A fintech (“Halcyon Ledger”) had strict reproducibility needs. Their engineers used Windows hosts, but their release pipeline depended on Linux packaging and kernel-adjacent behavior. They also had auditors who did not care about your feelings regarding tool choice.
The platform team made an unsexy decision: every project shipped with a pinned VirtualBox VM definition, a known distro version, and a documented networking mode. Developers could still use WSL for editing and quick scripts, but anything that mattered—packaging, integration testing, network behavior—ran in the VM.
This looked like “extra work” until a Windows update changed something in WSL’s networking behavior for a subset of laptops. Developers lost half a day chasing DNS and port-forwarding weirdness. The release pipeline didn’t flinch because it wasn’t running on the developer’s interpretation of WSL that week. It was running in the boring VM box that behaved the same as yesterday.
The savings came from predictability, not performance. The VM images were treated like artifacts: versioned, tested, and occasionally rotated. Nobody bragged about it. That’s how you know it worked.
Checklists / step-by-step plan
Checklist A: Decide WSL or VirtualBox for a new project
- Do you need inbound connections from other machines? If yes, prefer VirtualBox bridged networking.
- Do you need kernel modules, eBPF, or low-level networking? If yes, prefer VirtualBox.
- Is the workload small-file heavy? If yes, WSL is fine only if the project lives inside WSL ext4.
- Do you need USB devices or serial adapters? If yes, prefer VirtualBox USB passthrough.
- Is reproducibility across a team critical? If yes, prefer a VM baseline (VirtualBox) and treat it like an artifact.
- Is this mostly CLI tooling and local builds? If yes, WSL is usually the best experience.
Checklist B: Make WSL behave (the “don’t fight physics” setup)
- Keep code and dependency trees in
/home, not/mnt/c. - Use Windows editors that can open WSL paths without moving files back to NTFS.
- Verify systemd if you rely on it: check PID 1 and service status.
- Watch memory: if you’re swapping, tune resource limits or change platform.
- Be explicit about networking expectations: localhost dev is fine; LAN reachability is not a default feature.
Checklist C: Make VirtualBox boring and reliable
- Pick a network mode that matches reality:
- NAT for outbound-only dev.
- Bridged for “this VM is a real host on the LAN.”
- Host-only for isolated labs with explicit routing.
- Allocate resources explicitly: RAM and CPU count. Don’t rely on dynamic growth.
- Use snapshots sparingly and intentionally (before risky changes), not as a lifestyle.
- Decide how you share files. Shared folders are convenient but can be slower and less Linux-like than a native filesystem.
- Version your VM baseline for teams. If it matters, pin it.
Step-by-step: Migrating a project from WSL-on-/mnt/c to WSL ext4
- Create a workspace directory inside WSL:
mkdir -p ~/work. - Clone the repo inside WSL:
git clone ... ~/work/myapp. - Reinstall dependencies inside WSL (don’t reuse a Windows-side cache blindly).
- Update editor configuration to open the WSL path.
- Re-run the stress test and your build. If the delta isn’t obvious, your bottleneck isn’t the filesystem.
Step-by-step: Migrating “needs real host networking” from WSL to VirtualBox
- Create a VM with the distro you deploy (match major version).
- Set networking to Bridged Adapter.
- Install your stack and bind services to the guest NIC.
- Validate reachability from another LAN device.
- Document the VM settings (NIC mode, port expectations, firewall rules) in the repo so the next person doesn’t rediscover them via suffering.
FAQ
1) Is WSL 2 “a real VM”?
It uses a real Linux kernel in a VM-like environment. But it’s managed and integrated in ways that make it behave differently than a self-managed VM you control end-to-end.
2) If WSL 2 uses a real kernel, why do people still say it’s not real Linux?
Because “real Linux” usually implies control over kernel, modules, boot lifecycle, and predictable device/network behavior. WSL is real Linux userland on a managed kernel with integration constraints.
3) What’s the single biggest WSL performance mistake?
Putting your project (and especially dependency directories) on /mnt/c and then running Linux tools that create or stat thousands of files.
4) When is VirtualBox a bad idea?
When you need the tight Windows integration loop and you don’t need full-machine semantics. Also when your org’s host configuration makes VirtualBox slow due to competing virtualization layers—test on your baseline hardware before standardizing.
5) Can WSL replace VirtualBox for Kubernetes labs?
Sometimes. Single-node dev clusters can work well. If you need multi-node networking realism, ingress from other machines, or CNI behaviors that assume routable nodes, a VM lab is usually less surprising.
6) Why does localhost access work from Windows to WSL, but LAN access doesn’t?
Because Windows and WSL cooperate to forward some ports to localhost. That’s not the same thing as the WSL guest having a LAN-routable address.
7) Do I need systemd in WSL?
If your tooling expects systemd units, timers, journal behavior, or service dependencies, yes. If you’re just running shells and single processes, no. If you’re trying to test a server’s boot lifecycle, use a VM anyway.
8) What about USB devices and serial ports?
VirtualBox has a straightforward story for USB passthrough (with the right components). WSL can do some device scenarios, but if hardware access is central, a VM is typically the path of least regret.
9) Can I make WSL behave like bridged networking?
You can approximate it with port forwarding and Windows-side networking configuration, but it’s not the default model. If bridged is a requirement, choose a VM where bridged is a first-class knob.
10) Should teams standardize on one tool?
Standardize on outcomes. For coding, WSL is often the best Windows experience. For integration tests and reproducibility, a pinned VM baseline is usually the calmest choice.
Next steps you can do today
- Audit where your repos live. If they’re on
/mnt/cand you complain about performance, move one repo into/homeand re-measure. - Decide your networking requirement. If other machines must connect inbound, stop pretending NAT is fine. Use VirtualBox bridged mode for that workload.
- Run the stress tests and keep the results. Having a baseline number turns debates into engineering.
- Separate “dev shell” from “integration environment.” WSL for daily iteration; VirtualBox for realism and reproducibility when it matters.
- Write down the boundary rules. A two-page internal guide beats twenty Slack threads and a folklore-based onboarding process.
Pick WSL when you want speed of setup and a great Windows developer loop. Pick VirtualBox when you need a machine you can reason about—networking, storage, kernel behavior, and all. Use both when you’re serious: WSL for comfort, a VM for truth.