How to create a customizable alpine distribution with docker and other utilities to use with wsl

This article will help you to create a customizable alpine distribution that you can use on a normal Windows or a Windows server 2022. It includes how to install docker and other utilities.
Alpine is tiny, which is exactly why I use it for this. The whole exported image ends up around 100 MB, and once it's built you can import it on any machine and be running in a few seconds. That's the point of the whole thing: build it once, export it, reuse it.
Tested on Windows Server 2022. It might change on time.
Download alpine
First, download the official distribution on alpine. Choose x86_64 under Mini Root Filesystem.
Take the current version, not the one I used. I originally wrote this with 3.16.0, which is end of life since 2024. Nothing below depends on the version, only on it being the x86_64 mini root filesystem.
Import it into WSL
Make sure you already have WSL on your computer to continue. Then import the distribution you downloaded:
wsl.exe --import alpine "C:\Path-of-where-you-want-to-install-it" "C:\Path-of-your-downloaded-alpine" --version 2The --version 2 matters. Without it you get whatever your default WSL version is, and if that's WSL1 then docker, openrc and the boot command further down all fail, without telling you why.
Enter the distribution:
wsl.exe -d alpineYou're root, there is no sudo in the mini root filesystem and you don't need it.
Install the utilities
apk add --no-cache openrc docker docker-cli-compose nano curl wgetNote docker-cli-compose and not docker-compose. The docker-compose package is the old Python v1, which is dead since 2023, so you'd be installing something that no current tutorial matches. docker-cli-compose gives you the v2 plugin, so you call it docker compose without the dash.
Configure docker
Create the folder for docker's configuration:
mkdir -p /etc/dockerThen create /etc/docker/daemon.json:
nano /etc/docker/daemon.jsonand add the following:
{
"builder": { "gc": {"defaultKeepStorage": "20GB", "enabled": true } },
"experimental": false,
"features": { "buildkit": true },
"default-address-pools": [
{
"base": "172.20.0.0/16",
"size": 24
}
]
}To quit nano: CTRL + s, CTRL + x.
One thing about that address pool, because I got it wrong the first time I wrote this article. The private ranges are 10.0.0.0/8, 172.16.0.0/12 and 192.168.0.0/16. That middle one stops at 172.31.255.255. I had originally put 172.240.0.0/16 in there, which is public internet space. Docker accepts it without a single warning, containers talk to each other perfectly, and nothing breaks until the day something needs to reach a real host in that range. Then you have a problem that looks like anything except a bad config. Stay inside the private ranges.
Configure WSL
This part goes in two different files, on two different sides. I had them in one block before and that was wrong.
Inside alpine, /etc/wsl.conf, the per-distribution config:
[boot]
# start openrc at boot, doesn't work on Win10, must be Win11 or server 2022
command = "/usr/bin/env -i /usr/bin/unshare --pid --mount-proc --fork --propagation private -- sh -c 'exec /sbin/init'"On Windows, %UserProfile%\.wslconfig, which is global, for every WSL2 distribution:
[wsl2]
# bridge Hyper-V vmSwitch inside wsl2
networkingMode = bridged
vmSwitch = Replace-It-With-a-NIC-You-CreatedCheck on your setup:
networkingModeandvmSwitchare.wslconfigsettings per Microsoft's docs, not/etc/wsl.conf. WSL ignores settings it doesn't recognise silently, so if you had them in the wrong file you'd get no error at all, just no bridging. Verify on your machine which file is doing the work before you trust it.
For the bridged part you need Hyper-V installed and an external virtual switch created manually in Hyper-V Manager, bound to a real network adapter. That's what goes in vmSwitch. Without that switch, bridged mode won't come up. Also be aware bridged mode broke on some Windows versions when Microsoft added mirrored mode, and came back later. If it refuses to work, check wsl --version before you go debugging your config.
Waiting for the docker socket
The docker socket isn't there the instant the distribution comes up, and anything that runs too early just fails. In /etc/init.d/ create a file named wait_for_docker, no extension, and put this in it:
#!/bin/sh
# block until docker starts
FILE=/var/run/docker.sock
while true ; do
if [ -S "$FILE" ]; then
break
fi
sleep 0.5
doneThen make it executable:
chmod +x /etc/init.d/wait_for_dockerWithout this file my distribution didn't come up reliably, so keep it.
The one change from my original: there's now a sleep 0.5 in the loop. Before, the loop had nothing in it, which means it checked the socket as fast as the CPU would let it and pinned a core at 100% for as long as it was waiting. You never notice when the socket is already there, only when docker is slow or dead, which is exactly the case this file exists for.
Start docker at boot
rc-update add docker defaultThen shut everything down from Windows so the boot command takes effect:
wsl.exe --shutdownDoes it work ?
Go back in and check, because everything above is worth nothing if the daemon isn't up:
wsl.exe -d alpine
rc-status
docker version
docker run --rm hello-worldrc-status should show docker started. If docker version only prints the client and complains it can't reach the daemon, openrc didn't come up. That's the [boot] command in /etc/wsl.conf, and it needs Windows 11 or Server 2022. On Windows 10 you'll have to start it by hand with service docker start every time.
If you're using bridged networking, this is also where you check it:
ip addr show eth0You should see an address from your real LAN, not a 172.x NAT address.
Clean up and export
Remove the command history so your image doesn't ship with your typing in it:
rm -f ~/.ash_historyExit alpine:
exitThen export the distribution from Windows:
wsl.exe --export alpine alpine_customized.tarCheck on your setup: I used to compress this with
gzip alpine_customized.tarright after. That works if you have gzip in your PATH from git-bash or similar, but stock Windows has nogzipcommand, so it'll fail for most people. Either compress it inside alpine before exporting, or just leave the tar uncompressed,wsl --importtakes it either way.
Use it
This is the part that makes all of the above worth doing. On any machine, with your tar:
wsl.exe --import alpine-dev "C:\Path-of-where-you-want-it" alpine_customized.tar --version 2
wsl.exe -d alpine-devDocker, compose and your config are already there. Nothing to redo.
Enjoy !