WSL 2 Setup for Development

Submitted on May 30, 2020, 10:07 a.m.
WSL 2 Setup for Development

Here are a few notes, snippets, and links to my current Windows Subsystem for Linux 2 (WSL 2) setup for development.

Most of our current work is Drupal 8, Node.js, and React. I'd previously tried setting up all of our development tools under the first version of WSL, but as has been fairly well documented elsewhere, file I/O performance was a roadblock - in particular for Drupal 8 which can be filesystem I/O intensive during development.

With WSL 2 file I/O issues are reversed. WSL 2 distributions are based on a Microsoft-modified full Linux kernel run as a tightly integrated virtual machine. Internal file performance for WSL 2 is effectively as performant as the VM's kernel, and a lot faster than WSL 1. However, if you reach out to the default Windows 10 mount points under /mnt/c, /mnt/d etc., you'll find that NTFS performance is less than stellar. So the strategy in this case is to keep everything inside your WSL 2 filesystem. Each WSL 2 distribution (Ubuntu or other) is now stored in an ext4 formatted virtual hard drive - ext4.vhdx, dynamically allocated with an initial capacity of 256GB. What's more, the virtual hard drive can be exported, moved, and even imported into another computer which is great for backing up your distribution, as well as quickly setting up another machine.

Networking has also changed in WSL 2. At the time of writing, WSL 2 only supports a single dynamic NAT address shared across all distributions. At each reboot a new class B address will be assigned to WSL 2. Ports are automatically forwarded to the host, so this helps, but there are some extra steps required if you'd like to map host names to projects inside WSL 2. You also have to be careful about running services that may conflict with any other service on the same port - so for example, only run one web server on port 80, or only one ssh server on port 22.

The final piece of the puzzle in all of this is the newly developed Windows Terminal - a modern, ANSI, UTF8, Unicode, PTY-compatible GPU accelerated terminal emulator. Who'd of thought?

The WSL 2 docs are great - and so I'll only highlight a few of the things that helped smooth out my initial setup.

Thanks to this post here - https://github.com/microsoft/WSL/issues/4320#issuecomment-571758494 - here's how you can change the location of your WSL 2 distribution's virtual hard drive. I keep all of my VMs (and now WSL 2 virtual hard drives) in D:\VMs\<platform>\<distribution name> . Run the following commands in PowerShell - replacing distribution name and preferred location:

wsl --export Ubuntu ubuntu.tar
wsl --unregister Ubuntu
wsl --import Ubuntu D:\VMs\WSL\Ubuntu\ ubuntu.tar --version 2

Also at the time of writing, when you import a distribution, you may need to tell WSL 2 to start the distribution using the Linux user account you created when you first created the distribution. Thanks to another great post here - https://github.com/microsoft/WSL/issues/3974 combined with @merkuriy's handy WSL-SetDefaultUser function, I created a PowerShell script that imports the distribution, and resets the default user...

# Function to change the default user
Function WSL-SetDefaultUser ($distro, $user) { Get-ItemProperty Registry::HKEY_CURRENT_USER\Software\Microsoft\Windows\CurrentVersion\Lxss\*\ DistributionName | Where-Object -Property DistributionName -eq $distro | Set-ItemProperty -Name DefaultUid -Value ((wsl -d $distro -u $user -e id -u) | Out-String); };
# Usage
# WSL-SetDefaultUser <DistroName> <UserName>
$title = 'WSL Import'
$question = 'The current Ubuntu WSL system will be unregistered, and the new VHD imported. Are you sure you want to proceed?'
$choices = '&Yes', '&No'
$decision = $Host.UI.PromptForChoice($title, $question, $choices, 1)
if ($decision -eq 0) {
Write-Host 'Continuing with deregistration of Ubuntu, and import of replacement VHD'
wsl --unregister Ubuntu
Write-Host 'Importing VHD...'
wsl --import Ubuntu D:\VMs\WSL\Ubuntu\ D:\Temp\ubuntu.tar --version 2
Write-Host 'Setting default user...'
WSL-SetDefaultUser Ubuntu tony
} else {
Write-Host 'Operation canceled.'
}

Note that if your import fails with 'Unspecified error' (or other) - you may need to limit the amount of memory allocated to Vmmem. You can do this by creating a .wslconfig file located at %UserProfile%\.wslconfig .

Here are my .wslconfig settings...

[wsl2]
memory=16GB
swap=0
localhostForwarding=true

This issue is reported and documented here and here.

Another 'gottcha' - and again at the time of writing, is that WSL 2 does not currently support removable media - external USB drives, or thumb drives, and so if you're going to copy files or tarballs between distributions you have just a couple of options: 1) You can copy files via the host NTFS file system under the default mount points /mnt/c etc. Or 2) you can copy directly between distributions using SSH/SCP/Rsync. For this to work you're going to need to install an ssh server on one of the distros. As mentioned above, you'll need to be sure that this is the only SSH service running across all of your WSL distributions and your Windows 10 host (if not, you'll need to configure SSH to run on a port other than 22).

Here's how to setup SSH under an Ubuntu WSL 2 distribution:

sudo apt install ssh
cd /etc/ssh
sudo ssh-keygen -A

For convenience, and given you're doing all of this on a pair of WSL 2 distributions under your control, you can enable password authentication under SSH by editing  /etc/sshd.conf and configuring PasswordAuthentication yes

I recently installed the WSL 2 version of Ubuntu 20.04 LTS (Focal Fossa), and wanted to take my time bringing over my setup from my current 18.04 LTS release to my fresh 20.04 installation. After installing all of the core components (nginx, PHP, node.js etc.) I simply rsync'd my home directory over via:

rsync --dry-run -avze 'ssh -p 22' tony@172.29.122.46:/home/tony/ /home/tony

Note that you'll need to lookup the current WSL 2 IP address.

WSL 2 does not start any additional services by default. In fact - systemd is not available on a default installation. However, init scripts can be run via the service command, and so I've created a bash script that allows me to start and stop the services I need.

#!/bin/bash
sudo mkdir -p /var/run/mysqld
sudo chown mysql:mysql /var/run/mysqld
sudo service mysql "$@"
sudo service php7.3-fpm "$@"
sudo service nginx "$@"
SOLR_ULIMIT_CHECKS=false /opt/solr/bin/solr "$@"

I then alias the following commands in my .localrc or .local.fish files:

#wsl
alias wsl-up='~/Scripts/windows/wsl-exec-services.sh start'
alias wsl-down='~/Scripts/windows/wsl-exec-services.sh stop'

If I've rebooted, or I want to switch to another distribution running similar services, I run wsl-up, or wsl-down as needed.

Here's a separate post and a few tips for getting MySQL 8 installed under WSL 2

I run each project I'm working on under a tmux session (started with an aliased tmux launch script), with split panes and various tools. I'm also starting to like fish shell a lot (with Oh My Fish and bobthefish theme). My current setup can be found in my .dotfiles located here - https://github.com/58bits/dotfiles. Being able to see which IP address is currently assigned to WSL 2 is helpful, and so my current tmux setup places the IP address and uptime (effectively the time since the IP address was assigned) in the left status line. My tmux configuration is a simplified version of  Gregory Pakosz's amazing https://github.com/gpakosz/.tmux - with all of the helper shell scripts extracted into a separate directory, combined with a few additional helper scripts from Erik Westrup's excellent setup here https://github.com/erikw/tmux-powerline.

And lastly - while localhost and port forwarding will allow you to access services running under WSL 2 from Windows 10, we also map the apex record of a domain we've registered to 127.0.0.1. This means that on any machine, we can enter projectname.ourdomain.me and be directed to localhost, which will then forward the port 80 request to WSL 2. This is particularly useful for webserver configurations with multiple hostnames as virtual hosts all running under a single port. It's also sometimes useful to have a more specific hostname associated with the current WSL 2 ip address. We're an AWS Route 53 customer, and so for cases where we want developer- and project-specific hostnames, we run an AWS CLI bash script that calls aws route53 change-resource-record-sets  and automatically updates the Route 53 address for a specific host - for example: foo.dev01.ourdomain.me (ala DynDNS style).

We're still waiting for PHPStorm to fully support WSL 2. You can view the current issue list here https://youtrack.jetbrains.com/issues/WI?q=WSL%202. In the meantime, Visual Studio Code does a brilliant job of running a headless version of itself under WSL 2, to which the Windows 10 GUI version connects to seamlessly. Just type code . from any WSL 2 directory you'd like to start VS Code in, and VS Code will take care of the rest. Docker Desktop for Windows also has experimental WSL 2 support - which so far at least, has worked flawlessly despite the experimental flag.

I don't mind using bare-metal Linux installations, or Linux / Ubuntu under a VM. Until now VMware Workstation Pro has been our 'go to' desktop virtualization platform. In my case I still depend on several Microsoft productivity tools, and so for the first time ever I have a development environment that's completely integrated with my daily driver. Props to Microsoft for putting all of this together. It works, and I'm more productive as a result.