WebAssembly (WASM) is rapidly emerging as a transformative technology in the cloud-native ecosystem. Its binary instruction format, designed for execution on a stack-based Virtual Machine (VM), enables WASM modules to run seamlessly on any platform, unlocking unprecedented portability. Moreover, WASM promises near-native execution performance and enhanced security due to its “sandboxed” execution model.

A closer examination of WASM’s sandboxing reveals certain limitations. In WASM, modules execute within a stack-based VM that isolates them from direct interaction with the external environment. Communication with the outside world is mediated through imports and exports, with no direct access to system calls or underlying resources. Instead, the WebAssembly System Interface (WASI) employs a capability-based system to grant controlled access to external resources. Simply put, the WASM sandbox is essentially a software construct that enforces controlled interactions between the module and its environment.

While this sandboxing model offers strong software-based isolation, it still raises questions about the robustness of security, particularly given the findings of several research papers on WASM’s security vulnerabilities. To achieve stronger isolation, a more robust mechanism, such as hardware-based virtualization, is necessary. By executing each WASM module within its own VM, in the event of a WASM runtime escape, an attacker would also need to breach the VM — a significantly more challenging exercise.

On the other hand, VMs have the reputation of being heavyweight and unsuitable for lightweight workloads, such as running individual WASM modules. The overhead in terms of memory and CPU makes traditional virtualization impractical for this purpose.

Unikernels, a technology introduced in 2013 that has quietly matured over the years, could provide a viable alternative to minimize this overhead. Unikernels are highly specialized, lightweight operating system kernels designed to run a single application efficiently, eliminating the overhead of general-purpose OSes. They feature a minimal footprint, extremely fast boot times, and robust isolation. However, unikernels have historically faced criticism for being difficult to use, with application porting requiring significant effort.

This is where WASM complements unikernels beautifully. WASM’s portable binary representation is designed to execute on any compatible environment, including unikernels. This means WASM modules can run on unikernels with minimal adaptation, unlocking a powerful combination: the portability and flexibility of WASM, paired with the lightweight, fast-booting, and hardware-isolated properties of unikernel-based VMs.

By combining these technologies, we achieve a compelling solution: WASM modules running within small, fast, and truly isolated unikernel-powered VMs. This approach delivers the best of both worlds: strong isolation through virtualization and efficient resource utilization.

Fortunately, we are not alone in this vision. Several projects have already begun exploring this intersection of WASM, unikernels, and virtualization, demonstrating the potential of this powerful synergy. Specifically:

In particular:

  • Mewz is a unikernel framework designed to only run WASM applications.
  • Unikraft, an active unikernel project, provides support for WAMR.
  • OSv, one of the most well known unikernel framework supports the well known wasmer runtime.
  • Hermit-Wasm used RustyHermit, a unikernel written in Rust to execute WebAssembly applications.

So, we can already execute WASM applications in unikernels. But how do we manage these unikernels efficiently? Enter urunc, a container runtime tailored specifically for unikernels. As explained in a previous post, urunc functions as the runc equivalent for unikernels. With urunc, we can deploy, execute, and manage unikernels as seamlessly as managing typical containerized workloads.

Building on this foundation, we explored combining WASM, unikernels, and urunc to create a cohesive system stack that allows us to deploy, execute, and manage WASM applications running on unikernels as easily as traditional containers. To achieve this, we extended urunc to support additional unikernel frameworks, including Mewz and OSv, alongside its existing support for Unikraft. This diversity of unikernel frameworks gives us flexibility in running WASM applications on top of various unikernel environments, each offering unique advantages.

Let’s take a closer look at each one of them.

Mewz Link to heading

Mewz is a unikernel framework designed to support ultra-lightweight, high-performance workloads. In contrast to other WASM runtimes that execute on top of general purpose operating systems, Mewz is designed as a specialized kernel where WASM applications can execute. In addition, every WASM application executes on a separate Mewz instance, maintaining the single-purpose notion of unikernels. This makes Mewz particularly well-suited for scenarios where low latency and efficiency are critical.

According to the design of Mewz, the WASM application is transformed to an object file which is directly linked against the Mewz kernel. Therefore, when the Mewz kernel boots, it executes the linked WASM application. Mewz has partial support for WASI and it provides support for networking and an in-memory, read-only filesystem. In addition, Mewz has socket compatibility with WasmEdge,

Mewz and urunc Link to heading

Note: Adding support for Mewz in urunc is a straightforward task. Essentially, to boot Mewz unikernels, we need to tweak a few parameters in the Qemu command. This functionality will be available in the next planned release of urunc. Until then, we use the mewz branch in urunc when building it.

We build urunc with Mewz support with the following commands:

1git clone https://github.com/nubificus/urunc.git -b mewz
2docker run --rm -ti -v $PWD/urunc:/urunc -w /urunc golang:1.23 bash -c "git config --global --add safe.directory /urunc && make"
3sudo make -C urunc install

Note: Please refer to urunc for more details on installation and any relevant dependencies.

Building Mewz unikernels with a WASM module Link to heading

Mewz is built using Zig and it also requires wasker to transform a WASM binary in an elf object. Mewz will link the WASM binary along with its kernel and it will produce the unikernel. First step in this process is to create the wasm app. We use the app.wasm file built from the Mewz hello world example.

In order to make the whole process a bit simpler, we use the following Dockerfile to build the Mewz unikernel:

 1FROM debian:bookworm as builder
 2
 3ARG ZIG_VERSION=0.12.0
 4
 5RUN apt-get update && \
 6    apt-get install -y curl xz-utils git cmake libstdc++6 build-essential && \
 7    apt-get clean && \
 8    rm -rf /var/lib/apt/lists/*
 9
10RUN curl -SL https://ziglang.org/download/${ZIG_VERSION}/zig-linux-x86_64-${ZIG_VERSION}.tar.xz | tar -xJC /tmp && \
11	mv /tmp/zig-linux-x86_64-${ZIG_VERSION} /usr/bin/zig
12
13RUN curl -sSfL https://github.com/mewz-project/wasker/releases/download/v0.1.1/wasker-0.1.1-linux-$(uname -m)-gnu.tar.gz | tar -xzvC /usr/bin/ wasker && \
14        git clone https://github.com/mewz-project/mewz.git && cd mewz && \
15        git submodule update --init && \
16        ./scripts/build-newlib.sh && \
17        ./scripts/build-lwip.sh
18
19WORKDIR /mewz
20
21ENV PATH="/usr/bin/zig:${PATH}"
22
23COPY app.wasm .
24
25RUN wasker app.wasm && \
26        zig build -Dapp-obj=./wasm.o
27
28FROM scratch as artifacts
29COPY --from=builder /mewz/zig-out/bin/mewz.elf /mewz.elf

We can use the above Dockerfile to generate the Mewz unikernel binary with the following command:

1docker build -f Dockerfile -t mewz/builder  --target artifacts --output type=local,dest=./out .

The above command will build the Mewz unikernel and use the app.wasm file in our current directory as the WASM module for the unikernel.

Note: As app.wasm file, we used the helloworld.wat file from wasker.

However, Mewz produces an x86-64 image which Qemu refuses to load. For that purpose, we can perform the following trick, where we overwrite the e_machine field of the Elf header to EM_386:

1printf '\x03\x00' | dd of=out/mewz.elf bs=1 seek=18 count=2 conv=notrunc

Packaging Mewz unikernels for urunc Link to heading

After building the Mewz unikernel, we need to package it as an OCI image in order to deploy it over urunc. For that purpose, we will use pun, a tool we develop that uses buildkit to package unikernels in OCI images.

Let’s create the Containerfile with all the information for pun to package the Mewz unikernel.

1#syntax=harbor.nbfc.io/nubificus/pun:latest
2FROM scratch
3
4COPY out/mewz.elf /unikernel/mewz.elf
5
6LABEL "com.urunc.unikernel.binary"="/unikernel/mewz.elf"
7LABEL "com.urunc.unikernel.cmdline"="hello"
8LABEL "com.urunc.unikernel.unikernelType"="mewz"
9LABEL "com.urunc.unikernel.hypervisor"="qemu"

We can now build and package our mewz unikernel with:

1docker build -f Containerfile -t harbor.nbfc.io/nubificus/urunc/hello-mewz-qemu:test .

Running WASM over Mewz unikernels with urunc Link to heading

Finally, we can run the Mewz unikernel we created as any other container:

1docker run -m 512m --rm -ti --runtime io.containerd.urunc.v2 harbor.nbfc.io/nubificus/urunc/hello-mewz-qemu:test

Unikraft Link to heading

Unikraft is one of the most active unikernel projects. It allows the users to create lightweight, highly customizable and efficient unikernel images. Unikraft provides a POSIX-friendly environment, reducing the engineering effort of porting existing applications and libraries. Furthermore, Unikraft aims for binary compatibility with Linux, allowing the direct execution of Linux binaries on top of Unikraft.

However, in the case of WASM, the most interesting part in Unikraft is the support of wamr. Using wamr Unikraft is able to execute WASM modules.

Unikraft and urunc Link to heading

In the case of Unikraft urunc already has support and therefore no changes or different branches are required. We can directly use the main branch.

1$ git clone https://github.com/nubificus/urunc.git
2$ docker run --rm -ti -v $PWD/urunc:/urunc -w /urunc golang:1.23 bash -c "git config --global --add safe.directory /urunc && make"
3$ sudo make -C urunc install

Please refer to urunc for more detailed installation instructions.

Packaging Unikraft unikernels for urunc Link to heading

Similarly with Mewz, we will use pun to package the Unikraft unikernel for urunc. However, we need to build the Unikraft unikernel by ourselves and then package it with pun. We can build the Unikraft unikernel with the following commands:

 1git clone https://github.com/unikraft/app-wamr.git
 2cd app-wamr
 3mkdir workdir
 4pushd workdir
 5git clone https://github.com/unikraft/unikraft.git -b RELEASE-0.15.0
 6mkdir libs
 7git clone https://github.com/unikraft/lib-lwip.git -b RELEASE-0.15.0 libs/lwip
 8git clone https://github.com/unikraft/lib-musl.git -b RELEASE-0.15.0 libs/musl
 9git clone https://github.com/unikraft/lib-wamr.git -b RELEASE-0.15.0 libs/wamr
10popd
11cp defconfigs/qemu-x86_64-initrd .config
12make olddefconfig
13make

If everything goes well, the unikernel binary should be placed in ${PWD}/workdir/build/wamr_qemu-x86_64. In the case of Unikraft the WASM module can be a typical WASM file and it should be placed in the initrd. In the app-wamr example, Unikraft already has a hello world WASM module, under the rootfs directory. Therefore, we will transform the contents of this directory to a initrd. We can do that with the following commands:

1pushd rootfs
2find -depth -print | tac | bsdcpio -o --format newc > ../rootfs.cpio
3popd

The initrd file will be saved in the rootfs.cpio file.

At last, we can package the Unikraft unikernel with pun. For that purpose, we will use the following Containerfile:

 1#syntax=harbor.nbfc.io/nubificus/pun:latest
 2FROM scratch
 3
 4COPY workdir/build/wamr_qemu-x86_64 /unikernel/wamr_qemu-x86_64
 5COPY rootfs.cpio /unikernel/initrd
 6
 7LABEL com.urunc.unikernel.binary="/unikernel/wamr_qemu-x86_64"
 8LABEL "com.urunc.unikernel.cmdline"="/main.wasm"
 9LABEL "com.urunc.unikernel.initrd"="/unikernel/initrd"
10LABEL "com.urunc.unikernel.unikernelType"="unikraft"
11LABEL "com.urunc.unikernel.hypervisor"="qemu"
12LABEL "com.urunc.unikernel.version"="0.15.0"

We can now build and package our Unikraft unikernel with:

1docker build -f Containerfile -t nubificus/urunc/hello-wasm-unikraft-qemu:test .

Running WASM over Unikraft unikernels with urunc Link to heading

At last we can run the Unikraft unikernel we created as any other container:

1docker run --rm -ti --runtime io.containerd.urunc.v2 harbor.nbfc.io/nubificus/urunc/hello-wasm-unikraft-qemu:test

Summary and next steps Link to heading

WASM offers unparalleled portability and near-native execution, establishing itself as a key evolutionary technology in the cloud-native ecosystem. However, despite its sandboxed execution model, security vulnerabilities highlight the need for stronger isolation between workloads. In this context, we explore the potential of using unikernels to isolate WASM applications within VMs. By combining WASM’s portability with the fast boot times and robust isolation offered by unikernels, we can create a highly efficient and secure execution environment.

Furthermore, the ability of urunc to act as a container runtime for unikernels, streamlines the deployment and management of WASM applications running on unikernels. This enables us to deploy WASM applications with the same ease as containers, while simultaneously ensuring strong isolation with minimal overhead.

In this post, we reviewed two unikernel frameworks that already support WASM. Looking ahead, we plan to also provide more info regarding OSv as well. Additionally, we identified the need for a unified approach to building and packaging WASM unikernels as OCI images; for now, pun only addresses the latter. To address the former, we intend to complement pun with a new tool that will streamline the process of building and bundling all the binary artifacts into a single application kernel, ready to be deployed.

Stay tuned for more updates! In the meantime, please share your comments/suggestions/findings on github.