WSL

The Windows Subsystem for Linux (WSL) somewhat resembles a container hypervisor. A Windows user may have as many Linux distro instances as they wish, either created by the distro-launcher workflow (for the distros delivered through MS Store) or by importing a tarball containing a root filesystem. This page assumes the reader is familiar with WSL. To learn more about that, please visit the Microsoft documentation.

Requirements

  1. WSL interoperability must be enabled. The datasource needs to execute some Windows binaries to compute the possible locations of the user data files.

  2. WSL automount must be enabled. The datasource needs to access files in the Windows host filesystem.

  3. The init system must be aware of cloud-init. WSL has opt-in support for systemd, thus for distros that rely on it, such as Ubuntu, cloud-init will run automatically if systemd is enabled via the /etc/wsl.conf. The Ubuntu applications distributed via Microsoft Store enable systemd in the first boot, so no action is required if the user sets up a new instance by using them. Users of other distros may find it surprising that cloud-init doesn’t run automatically by default. At the time of this writing, only systemd distros are supported by the WSL datasource, although there is nothing hard-coded in the implementation code that requires it, so non-systemd distros may find ways to run cloud-init and make it just work.

Notice that requirements 1 and 2 are met by default, i.e. WSL grants those features enabled. Users can disable those features, though. That would prevent the datasource from working. For more information about how to configure WSL, check the official documentation.

User data configuration

The WSL datasource relies exclusively on the Windows filesystem as the provider of user data. Access to those files is provided by WSL itself unless disabled by the user, thus the datasource doesn’t require any special component running on the Windows host to provide such data.

User data can be supplied in any format supported by cloud-init, such as YAML cloud-config files or shell scripts. At runtime, the WSL datasource looks for user data in the following locations inside the Windows host filesystem, in the order specified below. The WSL datasource will be enabled if cloud-init discovers at least one of the applicable config files described below.

First, configurations from Ubuntu Pro/Landscape are checked for in the following paths:

  1. %USERPROFILE%\.ubuntupro\.cloud-init\<InstanceName>.user-data holds data provided by Landscape to configure a specific WSL instance. If this file is present, normal user-provided configurations are not looked for. This file is merged with (2) on a per-module basis. If this file is not present, then the first user-provided configuration will be used in its place.

  2. %USERPROFILE%\.ubuntupro\.cloud-init\agent.yaml holds data provided by the Ubuntu Pro for WSL agent. If this file is present, its modules will be merged with (1), overriding any conflicting modules. If (1) is not provided, then this file will be merged with any valid user-provided configuration instead. Exception is made for Landscape client config computer tags. If user provided data contains a value for landscape.client.tags it will be used instead of the one provided by the agent.yaml, which is treated as a default.

Then, if a file from (1) is not found, optional user-provided configuration will be looked for in the following order:

  1. %USERPROFILE%\.cloud-init\<InstanceName>.user-data holds user data for a specific instance configuration. The datasource resolves the name attributed by WSL to the instance being initialized and looks for this file before any of the subsequent alternatives. Example: sid-mlkit.user-data matches an instance named Sid-MLKit.

  2. %USERPROFILE%\.cloud-init\<ID>-<VERSION_ID>.user-data for the distro-specific configuration, matched by the distro ID and VERSION_ID entries as specified in /etc/os-release. If VERSION_ID is not present, then VERSION_CODENAME will be used instead. Example: ubuntu-22.04.user-data will affect any instance created from an Ubuntu 22.04 Jammy Jellyfish image if a more specific configuration file does not match.

  3. %USERPROFILE%\.cloud-init\<ID>-all.user-data for the distro-specific configuration, matched by the distro ID entry in /etc/os-release, regardless of the release version. Example: debian-all.user-data will affect any instance created from any Debian GNU/Linux image, regardless of which release, if a more specific configuration file does not match.

  4. %USERPROFILE%\.cloud-init\default.user-data for the configuration affecting all instances, regardless of which distro and release version, if a more specific configuration file does not match. That could be used, for example, to automatically create a user with the same name across all WSL instances a user may have.

Only the first match is loaded, and no config merging is done, even in the presence of errors. That avoids unexpected behaviour due to surprising merge scenarios. Also, notice that the file name casing is irrelevant since both the Windows file names, as well as the WSL distro names, are case-insensitive by default. If none are found, cloud-init remains disabled if no other configurations from previous steps were found.

Note

Some users may have configured case sensitivity for file names on Windows. Note that user data files will still be matched case-insensitively. If there are both InstanceName.user-data and instancename.user-data, which one will be chosen is arbitrary and should not be relied on. Thus it’s recommended to avoid that scenario to prevent confusion.

Since WSL instances are scoped by the Windows user, having the user data files inside the %USERPROFILE% directory (typically C:\Users\<USERNAME>) ensures that WSL instance initialization won’t be subject to naming conflicts if the Windows host is shared by multiple users.

Vendor and metadata

The current implementation doesn’t allow supplying vendor data. The reasoning is that vendor data adds layering, thus complexity, for no real benefit to the user. Supplying vendor data could be relevant to WSL itself, if the subsystem was aware of cloud-init and intended to leverage it, which is not the case to the best of our knowledge at the time of this writing.

Most of what metadata is intended for is not applicable under WSL, such as setting a hostname. Yet, the knowledge of metadata.instance-id is vital for cloud-init. So, this datasource provides a default value but also supports optionally sourcing metadata from a per-instance specific configuration file: %USERPROFILE%\.cloud-init\<InstanceName>.meta-data. If that file exists, it is a YAML-formatted file minimally providing a value for instance ID such as: instance-id: x-y-z. Advanced users looking to share snapshots or relaunch a snapshot where cloud-init is re-triggered, must run sudo cloud-init clean --logs on the instance before snapshot/export, or create the appropriate .meta-data file containing instance-id: some-new-instance-id.

Unsupported or restricted modules and features

Certain features of cloud-init and its modules either require further customization in the code to better fit the WSL platform or cannot be supported at all due to the constraints of that platform. When writing user-data config files, please check the following restrictions:

  • File paths in an include file must be Linux absolute paths.

    Users may be surprised with that requirement since the user data files are inside the Windows file system. But remember that cloud-init is still running inside a Linux instance, and the files referenced in the include user data file will be read by cloud-init, thus they must be represented with paths understandable inside the Linux instance. Most users will find their Windows system drive mounted as /mnt/c, so let’s consider that assumption in the following example:

C:\Users\Me\.cloud-init\noble-cpp.user-data

#include
/mnt/c/Users/me/.cloud-init/config.user-data
/mnt/c/Users/me/Downloads/cpp.yaml

When initializing an instance named Noble-Cpp cloud-init will find that include file, referring to files inside the Windows file system, and will load them effectively. A failure would happen if Windows paths were otherwise in the include file.

  • Network configuration is not supported.

    WSL has full control of the instances’ networking features and configuration. A limited set of options for networking is exposed to the user via /etc/wsl.conf. Those options don’t fit well with the networking model cloud-init expects or understands.

  • Set hostname.

    WSL automatically assigns the instance hostname and any attempt to change it will take effect only until the next boot when WSL takes over again. The user can set the desired hostname via /etc/wsl.conf, if necessary.

  • Default user.

    While creating users through cloud-init works as in any other platform, WSL has the concept of the default user, which is the user logged in by default. So, to create the default user with cloud-init, one must supply user data to the Users and Groups module and write the entry in /etc/wsl.conf to make that user the default. See the example:

#cloud-config
users:
- name: j
  gecos: Agent J
  groups: users,sudo,netdev,audio
  sudo: ALL=(ALL) NOPASSWD:ALL
  shell: /bin/bash
  lock_passwd: true

write_files:
- path: /etc/wsl.conf
  append: true
  contents: |
    [user]
    default=j
  • Disk setup, Growpart, Mounts and Resizefs.

    The root filesystem must have the layout expected by WSL. Other mount points may work, depending on how the hardware devices are exposed by the Windows host, and fstab processing during boot is subject to configuration via /etc/wsl.conf, so users should expect limited functionality.

  • GRUB dpkg.

    WSL controls the boot process, meaning that attempts to install and configure GRUB as any other bootloader won’t be effective.

  • Resolv conf and update etc/ hosts.

    WSL automatically generates those files by default, unless configured to behave otherwise in /etc/wsl.conf. Overwriting may work, but only until the next reboot.