WSL puts a real Ubuntu next to your Windows tools, and most .NET developers use it for one of three things: a Linux build to check before deploying, a database or service that only runs there, or Docker Desktop's engine, which lives in a WSL distribution whether you asked or not. This part installs a throwaway Ubuntu 24.04 distribution on WSL 2.7.10, gives it a user and systemd, installs the .NET SDK from Ubuntu's own feed, measures a build on the Windows drive against the Linux file system, reaches a Kestrel app in WSL from Windows, backs the whole distribution up and restores it. Nothing on the PC's real distribution changed; every output is quoted.
wsl --version,wsl -l -v, andTest-Path $env:USERPROFILE\.wslconfigwsl --install Ubuntu-24.04 --name ds-p2 --no-launch, thenwsl -d ds-p2 -- id: you are root until a user exists- Create a user, set
[user] default=in/etc/wsl.conf,wsl --terminate ds-p2, checkidandsystemctl is-system-running sudo apt-get update && sudo apt-get install -y dotnet-sdk-10.0, thendotnet --infodotnet new webtwice, under/mnt/c/...and under/home;time dotnet buildin each, twice- Run the app on
localhost:15920inside WSL and curl it from Windows wsl --export,wsl --importunder a new name,wsl --unregisterboth when done
A distribution of your own, in a minute
WSL 2.7 installs a second copy of a distribution under any name you like, with no Store:
wsl --install Ubuntu-24.04 --name ds-p2 --no-launch took 54 seconds and printed
Distribution successfully installed. It can be launched via 'wsl.exe -d ds-p2'. Because
of --no-launch the first-run prompt never ran, so id answered
uid=0(root), and the image already had systemd=true in its
/etc/wsl.conf. A user and a default:
[boot]
systemd=true
[user]
default=maria
wsl --terminate ds-p2 and the next command ran as maria with
systemctl is-system-running answering running. The documentation says to use
wsl --shutdown for systemd changes; terminating the one distribution was enough here, and
it matters, because wsl --shutdown stops every distribution, Docker Desktop's engine
included. The users
and passwords post has the rest of that story, including wsl -u root, which worked
here with no password and is the way back in when sudo's is forgotten.
The .NET SDK inside WSL
Microsoft's Ubuntu page now says ".NET is available in the Ubuntu package manager feeds. The Microsoft package repository no longer contains .NET packages for Ubuntu", and its table lists 10.0 in the built-in feed for 24.04. So the install is Ubuntu's own:
sudo apt-get update && sudo apt-get install -y dotnet-sdk-10.0
Thirty-three seconds, 164 MB downloaded, 624 MB on disk, and dotnet --info reported SDK
10.0.112 on runtime 10.0.12 under /usr/lib/dotnet. Note the feature band: the
Ubuntu feed ships the 10.0.1xx SDK, while the Windows side of this PC runs 10.0.402. A
global.json pinned to the Windows version with no roll-forward will not resolve inside
WSL, which is Part 1's lesson arriving from the other direction. And the first dotnet new
printed something Part 6 relies on: "Installed an ASP.NET Core HTTPS development certificate."
Paths in both directions
From Linux, the Windows drives are under /mnt/c, mounted over 9p with your user id, so
every file there looks like yours with mode 777. From Windows, the distribution is a UNC path:
\\wsl.localhost\ds-p2\home\maria, and the older \\wsl$ form still works.
wslpath translates: wslpath -w /home/maria printed
\\wsl.localhost\ds-p2\home\maria, and the other way round has a trap:
wsl -d ds-p2 -- wslpath -u 'C:\Users'
wslpath: C:Users
The command after -- goes through the distribution's shell, which eats the backslash.
wsl -d ds-p2 --exec wslpath -u 'C:\Users', a doubled backslash, or forward slashes all
printed /mnt/c/Users. A session started from a Windows folder begins in that folder as
/mnt/c/..., not in the Linux home.
Where the files are decides the build time
The same empty web project was built with time dotnet build -nologo -v q inside the
distribution, once under /mnt/c and once under /home/maria, twice each; then
again with fresh copies, the Linux side first, the build server shut down between rounds:
| Location | First build | Warm build | Order check, three builds |
|---|---|---|---|
/mnt/c, the Windows drive over 9p | 7.2 s | 3.8 s | 8.2, 4.9, 3.8 s |
/home/maria, ext4 | 5.9 s | 1.4 s | 6.5, 3.1, 1.8 s |
The Linux file system won every pairing in both orders, by about two to one on the warm build of a two-file project; a real solution has thousands of files to stat. The documentation's advice is the same: "For the fastest performance speed, store your files in the WSL file system if you are working in a Linux command line". Keep the repository where the tools that build it live, and clone it twice if you build on both sides.
A server in WSL, reached from Windows
dotnet run --urls http://localhost:15920 inside the distribution, and from Windows
curl.exe -sS -D - -o NUL http://localhost:15920/ answered HTTP/1.1 200 OK on
localhost, on 127.0.0.1 and on [::1]: WSL's localhost forwarding, on by default, carries a
port bound inside the distribution to the Windows loopback. The app kept the distribution alive
past the 15-second idle timeout as long as it ran. One thing bit the run: starting the server in the
background from a single wsl -- bash -c call with a trailing & lost the
process every time, before it even created its log file. Detaching it kept it alive:
wsl -d ds-p2 -- bash -lc "cd ~/WslLab && setsid -f nohup dotnet run --urls http://localhost:15920 > /tmp/web.log 2>&1 < /dev/null"
Stop it with wsl -d ds-p2 --exec pkill -f WslLab; the --exec matters, because
inside bash -c the pattern would match the bash itself. After the stop, Windows curl
reported an empty reply rather than a refusal for a moment, the forwarding side closing last.
Memory, processors, and a clock set to UTC
With no .wslconfig on this PC, the distribution saw all 24 processors and 15 GiB of the
31.8 GB, the documented defaults of "The same number of logical processors on Windows" and "50% of
total memory on Windows". Those two keys live in %UserProfile%\.wslconfig and apply to the
one WSL 2 virtual machine that every distribution shares, Docker Desktop's included; there is no
per-distribution memory setting, and the file "must be created" since it does not exist by default.
A side finding worth a line: the fresh distribution's clock said UTC on a PC set to India Standard
Time, because WSL asks for the legacy zone name Asia/Calcutta, which Ubuntu 24.04 keeps in
the tzdata-legacy package; installing that package and terminating the distribution
fixed date. Until then, every log inside WSL carries the wrong hour.
Backup: export and import
wsl --export ds-p2 ds-p2.tar wrote 2.51 GB in 48 seconds; wsl --import ds-p2-copy
from that tar took 18 seconds, and the copy started as maria, not root, because
/etc/wsl.conf travelled inside the tar with its [user] section.
wsl --unregister removed the copy and its folder in a fifth of a second. That pair is the
whole backup and restore story for a distribution, and the reason a wsl.conf belongs in
every distribution you care about.
Frequently asked
- How do I install the .NET SDK in WSL Ubuntu?
- On Ubuntu 24.04, from Ubuntu's own feed: sudo apt-get update and sudo apt-get install -y dotnet-sdk-10.0; Microsoft's package repository no longer carries .NET for Ubuntu. This installs the 10.0.1xx feature band under /usr/lib/dotnet, so a global.json pinned to a Windows 10.0.4xx SDK needs rollForward latestFeature to resolve inside WSL.
- Should I keep my project on /mnt/c or in the WSL file system?
- In the file system of the tools that build it. Inside WSL, a build of the same project under /home was about twice as fast as under /mnt/c on the warm build, in both orders tested, because /mnt/c is a 9p share of the Windows drive. The documentation says the same: store files in the WSL file system when working from a Linux command line.
- How do I reach a web app running in WSL from Windows?
- On localhost: WSL's localhost forwarding is on by default, so an app bound to localhost:15920 inside the distribution answers at http://localhost:15920 from Windows, on 127.0.0.1 and [::1] too. Start it detached with setsid -f if you launch it from a single wsl command, or it dies when that command returns.
Next: Part 3, ports and the firewall: who listens on what, the ranges Windows reserves, localhost against the LAN, and what the Windows Security Alert really does.