How WSL images are built in Launchpad
Despite being much different in format, size and contents, images of Ubuntu on WSL are built using the same machinery as Ubuntu Desktop or Server ISOs. This page explains the current image build pipeline for WSL images.
Note from original author
livecd-rootfs when implementing the tar-based distribution format for Ubuntu on WSL, I thought an overview of the pipeline would be interesting for those who are curious. — Carlos Nihelton (Ubuntu on WSL team).Contributions to this page from experts, including members of the Release team, are welcome to make this content more complete.
The Stack
- Ubuntu CD image (
ubuntu-cdimage) is used to produce daily builds and releases of Ubuntu and its derivatives, it consists of a significantly-modified version ofdebian-cdand some fairly extensive wrapper scripts[1]. It is the highest level orchestrator of the build pipeline. - Launchpad is the infrastructure where everything happens. It offers an API that
ubuntu-cdimagecalls into to queue build jobs, monitor job states, and store artefacts. - Launchpad builder service (
launchpad-buildd) [2] is the provisioner of worker sandboxes, provisions LXD containers or virtual machines inside which the image build process happens. - The filesystem build system (
livecd-rootfs) historically created for the Ubuntu Desktop CD images is nowadays a configuration machinery composed of thousands of lines of shell scripts that translates Ubuntu projects into rules and hooks that ultimately dictates the final shape of the artefact being built.[3] - The Debian upstream live-build is the assembly engine common to Debian derivatives. It runs
debootstrap, installs packages and outputs the root file system tarballs.[4] It provides standard build commands, such aslb_bootstrap,lb_chroot,lb_binaryetc and integrates with the "preparer" by calls oflb_configwith the argument--iso-preparer livecd-rootfs(in our case) to handover control to that package at the appropriate steps.
Key events
The highest level orchestration belongs to ubuntu-cdimage, triggered by cron jobs (such as the daily builds) or by the Release team at specific situations. It requests Launchpad API to spin up a livefs build job across all the required architectures related to that trigger. The builder service provisions workers (usually LXD containers these days). Provisioning includes redirecting apt sources to Canonical internal mirrors for both speed and confinement, and finally triggering the command buildlivefs passing the specific --project flag. That causes installation of build time dependencies, such as debootstrap, germinate, live-build, livecd-rootfs and snapd.
Inside the already provisioned worker we run germinate to fetch the distribution package indices and seed definitions. Here the concept of a project (first placed in the buildlivefs command line) dictates the customization points, it maps to the Ubuntu product being built, as we can see in multiple places in the livecd-rootfs auto/config and auto/build scripts, such as in the snippet below:
...
if [ -z "${IMAGEFORMAT:-}" ]; then
case $PROJECT:${SUBPROJECT:-} in
ubuntu-cpc:*|ubuntu:desktop-preinstalled|ubuntu-wsl:*)
case $ARCH+${SUBARCH:-} in
arm64+raspi)
# All raspi images use ubuntu-image.
IMAGEFORMAT=ubuntu-image
;;
arm64+tegra|arm64+tegra-igx|arm64+tegra-jetson)
# Pre-installed Tegra images use
# ubuntu-image
IMAGEFORMAT=ubuntu-image
;;
*)
IMAGEFORMAT=ext4
;;
esac
;;
ubuntu-server:live|ubuntu-mini-iso:|ubuntu-test-iso:|ubuntu-core-installer:*)
IMAGEFORMAT=plain
;;
esac
fi
...
Each Ubuntu product maps to a project in this domain, and to a seed that germinate processes to determine the list of packages that composes the image being built[5]. WSL has its own seed.
Once seed germination is resolved, live-build kicks in, runs lb_config using the project specific (WSL in our case) rules defined in livecd-rootfs (for example --chroot-filesystem ext4 --bootloader none --initramfs none --binary-images none --linux-packages none). lb_bootstrap invokes debootstrap to download, extract and configure core system packages in an isolated chroot further customized by the hooks coming from livecd-rootfs (live-build/ubuntu-wsl/hooks/) named following a simple convention: *.chroot targets the chroot environment and *.binary targets the tarball artefact. For example, the hook that compresses the tarball and rename it to *.wsl looks like the following:
#!/bin/bash -eu
# vi: ts=4 expandtab
#
# Generate the compressed root directory for WSL manually.
# Type "plain" unfortunately does not execute lb_binary_* helpers.
case $ARCH in
amd64|arm64)
;;
*)
echo "WSL root tarballs are not generated for $ARCH."
exit 0;;
esac
## remove attributes not supported by WSL's tar
setfattr -x system.posix_acl_access chroot/var/log/journal
setfattr -x system.posix_acl_default chroot/var/log/journal
# Create the rootfs as a .wsl extension
cd chroot
tar --xattrs --sort=name -czf ../livecd.$PROJECT${SUBARCH:+-$SUBARCH}.wsl *
cd ..
# Remove initial ext4-formatted fs
rm -f binary/boot/filesystem.ext4
The output of the Launchpad worker instance is still a tarball. The final steps of the process consist in ubuntu-cdimage retrieving those artefacts, and prepare the final publication. For server and desktop images that used to run debian-cd and xorriso to combine the rootfs with bootloaders and offline package pools into an ISO, although a somewhat recent proposal aimed to modify livecd-rootfs to output the ISO directly was merged in 2026 and seems to be working[6]. For the WSL case, since the tarball is all we need, the ISO step is skipped completely and the image is published to cdimage.ubuntu.com.
Can we do it outside of ubuntu-cdimage?
Yes, there are ways to run most of the machinery locally (for troubleshooting livecd-rootfs for example), but I'm not able to describe how to do it. An easier way is to leverage Launchpad custom livefs'es[7]. Members of the launchpad-livefs-builders team in Launchpad can create an endpoint like https://launchpad.net/~<TEAM>/+livefs, populating the necessary metadata (such as which Ubuntu release, and PPAs to include if any) or programmatically, via the Launchpad API, similar to the snippet below (the exact details may change over time, always refer to the official API documentation[8]):
from launchpadlib.launchpad import Launchpad
lp = Launchpad.login_with('my-app', 'production', version='devel')
team = lp.people['<YOUR-TEAM>']
distro_series = lp.distributions['ubuntu'].getSeries(name='<RELEASE>')
# Create the LiveFS object
livefs = lp.livefses.new(
name='<YOUR_CHOICE>',
owner=team,
distro_series=distro_series
)
References
- ↑ https://launchpad.net/ubuntu-cdimage
- ↑ https://launchpad.net/launchpad-buildd
- ↑ https://launchpad.net/livecd-rootfs
- ↑ https://salsa.debian.org/live-team/live-build
- ↑ https://launchpad.net/ubuntu-seeds
- ↑ https://discourse.ubuntu.com/t/spec-make-iso-in-livefs-build/73349/10
- ↑ https://ubuntu.com/docs/launchpad/developer/explanation/core-components-and-set-up/building-live-filesystems/
- ↑ https://launchpad.net/+apidoc/devel.html#livefs