WSL2 Containers
Tags: wsl
Published: 2020-08-07 16:05:55

Because, why not?

Playing around with WSL2, I figured it could be used to make one-off build containers. In a similar way I usually use docker.

For example, building TDLib for Telegram Messenger.

My `run.cmd` is simple:

```cmd
@echo off
wsl --import alpine-build .\VM alpine-minirootfs-3.12.0-x86_64.tar.gz
wsl -d alpine-build /bin/sh /mnt/e/Temp/TDLib/build.sh
wsl --unregister alpine-build
```

1. Install alpine distribution by running `wsl --import`
2. Execute the build script
3. Unregister the distribution

Build script, `build.sh` , is made using TDLib [Build instrunction generator](https://tdlib.github.io/td/build.html), with the addition of the `OUT_DIR` variable just to make it easier. 

```sh
OUT_DIR=/mnt/e/Temp/TDLib/lib

cd ~
apk update
apk upgrade
apk add --update alpine-sdk linux-headers git zlib-dev openssl-dev gperf php php-ctype cmake
git clone https://github.com/tdlib/td.git
cd td
git checkout v1.6.0
rm -rf build
mkdir build
cd build
export CXXFLAGS=""
cmake -DCMAKE_BUILD_TYPE=Release -DCMAKE_INSTALL_PREFIX:PATH=$OUT_DIR ..
cmake --build . --target install
cd ..
cd ..
ls -l $OUT_DIR
```

Running it and it works.

```
PS E:\Temp\TDLib> .\run.cmd
fetch http://dl-cdn.alpinelinux.org/alpine/v3.12/main/x86_64/APKINDEX.tar.gz
fetch http://dl-cdn.alpinelinux.org/alpine/v3.12/community/x86_64/APKINDEX.tar.gz
v3.12.0-214-g4076381e49 [http://dl-cdn.alpinelinux.org/alpine/v3.12/main]
v3.12.0-213-gfedcac4c0f [http://dl-cdn.alpinelinux.org/alpine/v3.12/community]
OK: 12749 distinct packages available
(1/6) Upgrading musl (1.1.24-r8 -> 1.1.24-r9)
<snip>
-- The CXX compiler identification is GNU 9.3.0
-- The C compiler identification is GNU 9.3.0
-- Check for working CXX compiler: /usr/bin/c++
-- Check for working CXX compiler: /usr/bin/c++ - works
<snip>
-- Configuring done
-- Generating done
-- Build files have been written to: /root/td/build
Scanning dependencies of target tdsqlite
[  0%] Building C object sqlite/CMakeFiles/tdsqlite.dir/sqlite/sqlite3.c.o
[  0%] Linking C static library libtdsqlite.a
[  0%] Built target tdsqlite
<snip>
Scanning dependencies of target bench_queue
[100%] Building CXX object benchmark/CMakeFiles/bench_queue.dir/bench_queue.cpp.o
[100%] Linking CXX executable bench_queue
[100%] Built target bench_queue
Install the project...
-- Install configuration: "Release"
-- Installing: /mnt/e/Temp/TDLib/lib/lib/libtdjson.so.1.6.0
-- Installing: /mnt/e/Temp/TDLib/lib/lib/libtdjson.so
-- Installing: /mnt/e/Temp/TDLib/lib/lib/libtdjson_static.a
-- Installing: /mnt/e/Temp/TDLib/lib/lib/libtdjson_private.a
<snip>
total 0
drwxrwxrwx    1 root     root           512 Aug  7 14:03 include
drwxrwxrwx    1 root     root           512 Aug  7 14:03 lib
Unregistering...
PS E:\Temp\TDLib>
```

Not as versatile as `docker run` but does the job.

## Using differencing disks

Using differencing disks, I can have all build tools installed in the base vhdx and spawn "build container"  just for a single build.

For example, based on above, I've created base

```cmd
PS E:\Temp> wsl --import Alpine-BuildBase E:\WSL2\Alpine-BuildBase E:\WSL2\alpine-minirootfs-3.12.0-x86_64.tar.gz
```

And installed everything I need

```shell
apk update
apk upgrade
apk add --update alpine-sdk linux-headers git zlib-dev openssl-dev gperf php php-ctype cmake
```

Now my `build.sh` omits tools installation and looks like:

```shell
OUT_DIR=/mnt/e/Temp/TDLib/lib

cd ~
git clone https://github.com/tdlib/td.git
cd td
git checkout v1.6.0
rm -rf build
mkdir build
cd build
export CXXFLAGS=""
cmake -DCMAKE_BUILD_TYPE=Release -DCMAKE_INSTALL_PREFIX:PATH=$OUT_DIR ..
cmake --build . --target install
cd ..
cd ..
ls -l $OUT_DIR
```

And the final part, `run.ps1` PowerShell script that will:

1. Create differencing vhdx
2. Make a registry key for the linux distribution "container"
3. Execute the build script
4. Unregister the distribution

```powershell
#Must be run as an Administrator
if (!([Security.Principal.WindowsPrincipal][Security.Principal.WindowsIdentity]::GetCurrent()).IsInRole([Security.Principal.WindowsBuiltInRole]::Administrator)) {
    Start-Process PowerShell -Verb RunAs "-NoProfile -ExecutionPolicy Bypass -Command `"cd '$pwd'; & '$PSCommandPath';`"";
    exit;
}

$DISTRO_NAME = "alpine-build"

#Create a new differencing VHD
New-Item -Path VM -ItemType Directory -Force | Out-Null
New-VHD -ParentPath E:\WSL2\Alpine-BuildBase\ext4.vhdx -Path .\VM\ext4.vhdx -Differencing -BlockSizeBytes 1048576

#Add a registry key
$guid = New-Guid
$RegKey = "HKCU:\SOFTWARE\Microsoft\Windows\CurrentVersion\Lxss\" + $guid.ToString("B")
New-Item -Path $RegKey
New-ItemProperty -Path $RegKey -Name BasePath -PropertyType String -Value $PWD\VM | Out-Null
New-ItemProperty -Path $RegKey -Name DefaultUid -PropertyType DWord -Value 0 | Out-Null
New-ItemProperty -Path $RegKey -Name DistributionName -PropertyType String -Value $DISTRO_NAME | Out-Null
New-ItemProperty -Path $RegKey -Name Flags -PropertyType DWord -Value 15 | Out-Null
New-ItemProperty -Path $RegKey -Name State -PropertyType DWord -Value 1 | Out-Null
New-ItemProperty -Path $RegKey -Name Version -PropertyType DWord -Value 2 | Out-Null

#Run the build script
wsl -d $DISTRO_NAME /bin/sh /mnt/e/Temp/TDLib/build.sh

#Unregister the distribution
wsl --unregister $DISTRO_NAME

#Pause
Read-Host -Prompt "Press Enter key to continue"
```



## Just testing 

To make sure above script works as expected instead of running the build script (line 25 above) I can swap it with

```batch
wsl -d $DISTRO_NAME /bin/sh -c "date && cat /etc/*-release"
```

Result is 

```
ComputerName            : PC123
Path                    : E:\Temp\TDLib\VM\ext4.vhdx
VhdFormat               : VHDX
VhdType                 : Differencing
FileSize                : 6291456
Size                    : 274877906944
MinimumSize             :
LogicalSectorSize       : 512
PhysicalSectorSize      : 4096
BlockSize               : 1048576
ParentPath              : E:\WSL2\Alpine-BuildBase\ext4.vhdx
DiskIdentifier          : A96ADE57-4838-48EA-97A9-DE02790ADCB4
FragmentationPercentage :
Alignment               : 1
Attached                : False
DiskNumber              :
IsPMEMCompatible        : False
AddressAbstractionType  : None
Number                  :

Property      : {}
PSPath        : Microsoft.PowerShell.Core\Registry::HKEY_CURRENT_USER\SOFTWARE\Microsoft\Windows\CurrentVersion\Lxss\{4
                e9ced54-7d28-48d2-92de-1df93ddc55a9}
PSParentPath  : Microsoft.PowerShell.Core\Registry::HKEY_CURRENT_USER\SOFTWARE\Microsoft\Windows\CurrentVersion\Lxss
PSChildName   : {4e9ced54-7d28-48d2-92de-1df93ddc55a9}
PSDrive       : HKCU
PSProvider    : Microsoft.PowerShell.Core\Registry
PSIsContainer : True
SubKeyCount   : 0
View          : Default
Handle        : Microsoft.Win32.SafeHandles.SafeRegistryHandle
ValueCount    : 0
Name          : HKEY_CURRENT_USER\SOFTWARE\Microsoft\Windows\CurrentVersion\Lxss\{4e9ced54-7d28-48d2-92de-1df93ddc55a9}

Fri Aug  7 15:27:17 UTC 2020
3.12.0
NAME="Alpine Linux"
ID=alpine
VERSION_ID=3.12.0
PRETTY_NAME="Alpine Linux v3.12"
HOME_URL="https://alpinelinux.org/"
BUG_REPORT_URL="https://bugs.alpinelinux.org/"
Unregistering...
Press Enter key to continue:
```



## Conclusion

I'm not sure if this has a practical value. Maybe in scenarios where it is easier to have a single file virtual disk rather than a docker container stored in some registry. But it was an interesting concept to explore. Let me know if you find it useful.