When scripting VM or container images, it’s easy to inadvertently rm -rf /, so it’s desirable to mount host drives read only. It’s also nice to have a static IP for accessing your machine. The default WSL2 configuration uses a dynamic IP. It was actually relatively easy, but it did take me a day or so of work. I’m using the official distro.
Distributor ID: Ubuntu Description: Ubuntu 22.04.2 LTS Release: 22.04 Codename: jammy
Create a /etc/wsl.conf that uses /etc/fstab. This will allow us to custom mount drives however we want. The appendWindowsPath part automatically adds the paths for powershell, ssh, and other standard Windows executables. For this to work, you need to mount drive C. To set a static IP, set generateHosts to false so Windows doesn’t overwrite the hosts file. I’ll get to resolv.conf below.
[automount] enabled = true root = /mnt options = "metadata" mountFsTab = true [network] generateResolvConf = false generateHosts = false hostname = ubuntu [interop] appendWindowsPath = true [user] default = myuser [boot] systemd = true
Now create /etc/fstab with the directories you want. Because we enabled automount, it will mount C automatically, but it won’t be read only. The second entry in fstab makes it read only. Note that you can still modify your host machine by running powershell.exe “command”, but it’s a lot harder to damage by accident.
# UNCONFIGURED FSTAB FOR BASE SYSTEM C:/mystuff /home/mystuff drvfs defaults,ro 0 0 C: /mnt/c drvfs defaults,ro 0 0
To use a static IP, we’ll need a virtual network adapter. Do the following as admin. The first command will only work once WSL is up and running. You’ll have to add a rule to the firewall to allow traffic coming from WSL. Without this you won’t even be able to ping the gateway. You may need to kill and restart sshd. Initially I removed the dynamic IP windows attached to the adapter. Don’t do this. See below.
netsh.exe interface ip add address "vEthernet (WSL)" 192.168.2.1 255.255.255.0 New-NetFirewallRule -Name 'WSL' -DisplayName 'WSL' -InterfaceAlias 'vEthernet (WSL)' -Direction Inbound -Action Allow
To temporarily set an IP, execute the following.
ip addr add 192.168.2.5/24 broadcast 192.168.2.255 dev eth0 label eth0:1
To set it permanently, edit /etc/systemd/network/10-eth0.network and run systemctl enable systemd-networkd.
[Match] Name=eth0 [Network] Address=192.168.2.5/24 Gateway=192.168.2.1 DHCP=no
I ran into an issue at this point with contacting the default nameserver. To fix this, you have to set generateResolvConf to false and add the following to /etc/systemd/resolved.conf. After reboot, check the result with resolvectl. Initially I tried this with the resolvconf package. But I had to run resolvconf -u after each boot to get it to generate resolv.conf.
[Resolve] # Google's public DNS DNS=184.108.40.206 220.127.116.11
You need to link /etc/resolve.conf to systemd’s version.
ln -s /run/systemd/resolve/resolv.conf /etc/resolv.conf
Now update /etc/hosts with the new IP.
Let yourself log in without a password. You can log in with a different user with the -u flag (e.g. wsl -d ubuntu -u myuser).
echo "myuser ALL=(ALL) NOPASSWD:ALL" | sudo tee /etc/sudoers.d/myuser
If you see error messages like the following, something went wrong mapping the drives, and there’s probably other issues as well. It may crash after a short while. Usually this happens if I shutdown from within the container instead of using wsl –shutdown. The only thing to do is restart.
<3>WSL (819) ERROR: UtilTranslatePathList:2803: Failed to translate C:\Windows\system32
If you get error messages about insufficient memory, you can try adding the following to C:\Users\myuser.wslconfig.
[wsl2] memory=4GB processors=4
I’ve create an ansible script to do this for Rocky Linux 9. Download Rocky-9-container-Base. Place the files below in a directory on the host and follow the steps at the top of the YAML file, making sure to change IPs, user names, paths, etc.
Add WSL Network Adapter
The vEthernet (WSL) adapter does not get created until you start WSL after each reboot. There are a couple of ways to automate this.
Method 1: Startup
Put the create_wsl_network.bat file in a convenient place like your user home. Create a shortcut to the batch file in the same directory. Right click the shortcut, select Properties -> Advanced, and check Run as administrator. Type windows key + R and enter shell:startup. Create a batch file with a single line that has the full path to the shortcut (e.g. C:\Users\myuser\WSL_Network.lnk). Don’t ask me why this has to be so complicated. The only problem with doing it this way is that it will ask you for permission to run the batch file every time you log in.
Method 2: Task Scheduler
Start the task scheduler and select Create Task. Check Run with highest privileges on the General tab and enter a name for the task at the top. Under the Triggers tab, click New, and select At log on from the drop down at the top. On the Actions tab, click New, and browse to create_wsl_network.bat. Save.
If you’re using Windows Terminal, you can open a tab directly to a WSL instance. However, there are a lot of profiles in the dropdown. You can hide a profile by going to Settings -> Profiles -> Specific Profile -> Hide profile from dropdown. If you want to save your terminal settings, you can find the settings.json in C:\Users\username\AppData\Local\Packages\Microsoft.WindowsTerminal_8wekyb3d8bbwe\LocalState.
Your WSL instances are mounted as network drives. To access an instance called rocky from Powershell, for example, cd \\wsl.localhost\rocky.
You may also want to set the Hyper-V Host Compute Service to start automatically. It isn’t necessary though since it will start when you start WSL.
Shortcut to WSL2 GUI Application
Create a new shortcut, e.g.:
C:\Windows\System32\wsl.exe -d rocky -e bash -ic "nohup ~/run-emacs.sh &"