Short answer:
The smallest bootable (without errors or warnings) WSL rootfs will consist of three files:
/main
: Your statically-linked application. It can be named whatever you want, as long as the name matches what is in passwd
.
/etc/passwd
: Defines the application (i.e. shell) to load for the default user.
/etc/wsl.conf
: To suppress normal WSL functionality and (optionally) define the non-root user.
More detail:
This probably isn't exactly what you are wanting, but it will hopefully meet your needs.
To start with, the entry point for WSL (the first time a Linux ELF binary is started inside the instance) seems to be its /init
binary, which, in addition to some "normal" Linux init process tasks, sets up some of the Windows-interop functionality. To my knowledge, it cannot currently be changed. As far as I can tell, for WSL1, it is injected into the instance by the LXSS manager when starting a WSL instance.
Note: WSL2 might be slightly different in this regard, as it does seem to use a kernel-processed initrd to load /init
. It is possible to override the kernel command-line, but that would impact all WSL2 instances, so it's probably not a practical solution.
It's not quite clear from your question whether you want the "default application" to:
- Run as the default application/shell every time
wsl -d static
is run, even if it was already running.
- Or just run once when starting the WSL1 instance for the first time.
I believe you are looking for the first option.
Run as the default application
In the first case, the standard WSL1 /init
process might get you to where you need to be. As part of the startup, as you would expect, it reads /etc/passwd
to determine the user shell to start. It also reads /etc/wsl.conf
to determine the default user ID (but falls back to the registry if there is no default user set in wsl.conf
).
So, to start a different application (let's call it main
), you can:
Place the binary in the root directory of your image.
Set the application as the "shell" of the root user in a single-line /etc/passwd
:
user:x:1000:1000:user:/:/main
Side-note that this also sets the home directory to /
so we don't have to create another directory.
Define a etc/wsl.conf
with the following contents:
[user]
default=user
[automount]
enabled=false
mountFsTab=false
[interop]
appendWindowsPath=false
This will prevent WSL from performing the following startup tasks, which would produce an error without additional image support:
- Mounting Windows drives into the instance
- Attempting to process
/etc/fstab
(since we have no mount
command in the image).
- Appending Windows paths (since our instance won't have access to the Windows drives)
It also sets the default user to the UID 1000 user we created in /etc/passwd
. This isn't strictly necessary - There's likely no concern with running as root in this single-use instance, but I've included a non-root user as a "best practice".
That should be it. The smallest bootable WSL rootfs will consist of just those three files:
/etc/wsl.conf
/etc/passwd
/main
This will work on WSL1 as well as WSL2, although for WSL2, you should invoke with wsl ~ -d static
to make sure that it doesn't try to start on a Windows drive that it can't access. Otherwise, you'll receive an init error, but your application will still be invoked.
Run once
If you are looking for something that will, for instance, start up a daemon when the instance is started for the first time, then there are a few alternatives that I document in this answer. If you are on Windows 11, then there's a built-in mechanism via /etc/wsl.conf
. Otherwise, on Windows 10, you'll probably need to include some binary that can handle conditional logic. Something like execline would probably be perfect for this, but I've had issues with it under WSL2, at least, and I'm not sure that it would run under WSL1 (but it might).
Side-note for WSL1/musl
musl is a commonly used alternative libc implementation. For instance, Rust (AFAICT), can only generate truly statically-linked executables using musl. Note, however, that WSL1 cannot run musl-based statically linked binaries.
WSL2 can handle them just fine.