Running Wasm in a container


Docker recently announced experimental support for running Wasm modules (see Announcing Docker+Wasm Technical Preview 2). In this blog post, I explain what this means and how to run a Wasm module in Docker.

Why run Wasm in a container?

In my Exploring WebAssembly outside the browser post, I mentioned how Wasm is faster, smaller, more secure, and more portable than a container. You might be wondering: Why take something faster, smaller, more secure, and more portable and run it in a container?

That’s a very good question. By running Wasm in a container, you’re combining the benefits of both. You get the benefits of a Wasm runtime but you also get the benefits of containers with namespaces and cgroups. More importantly, containers have a vast ecosystem around them with Docker, Docker Hub, Kubernetes etc. It’d be a shame to throw away this ecosystem and reinvent a new one just for Wasm. Instead, Wasm can play in this ecosystem.

In my view, the future will be Wasm modules running as is, without having to wrap them in containers. The current state is probably a stepping stone to that future but we’ll see how this space evolves.

Before we dive into details, let’s first do a recap of how containers are managed today.

containerd and runc

Docker (and Kubernetes) relies on a container runtime called containerd which in turn relies on runc to manage containers. At the high level, it looks like this:

containerd and runc

Technically, there’s a shim between containerd and runc but let’s leave that out of our discussion right now.

runwasi

Runwasi Logo

To run Wasm modules, you need a Wasm runtime. That’s when runwasi comes into picture. runwasi is a project to integrate Wasm runtimes with containerd to enable containerd to manage the lifecycle of Wasm modules. It supports Wasm runtimes such as wasmtime and wasmedge.

With runwasi, Docker and Kubernetes can now use containerd to run Wasm modules alongside with regular containers:

containerd and runwasi

Wasm runtime support in Docker

Now that we understand how containerd can manage Wasm modules using runwasi, let’s look into what Docker actually supports today.

In the Announcing Docker+Wasm Technical Preview 2 post, Docker announced support for 3 new Wasm runtimes, in addition to the existing runtime. This is the list of Wasm runtimes supported by Docker via the runwasi library:

  • wasmedge from CNCNF
  • wasmtime from Bytecode Alliance
  • spin from Fermyon
  • slight from Deislabs

Run Rust on Wasm in Docker

Next, let’s look into how to run a Rust app as a Wasm module in a Wasm runtime in Docker.

Create a Wasm module

First, make sure Rust is ready to compile to Wasm:

rustup target add wasm32-wasi

Create a HelloWorld Rust app:

cargo new hello-wasm

Change the message in main.rs to print some messages with some waiting in between:

use std::{thread, time};

fn main() {
    println!("Hello, Wasm before!");
    let duration = time::Duration::from_secs(10);
    thread::sleep(duration);
    println!("Hello, Wasm after!");
}

Build for Wasm-Wasi:

cargo build --target wasm32-wasi

As a test, run in a Wasm runtime such as wasmtime:

wasmtime target/wasm32-wasi/debug/hello-wasm.wasm

Hello, Wasm before!
Hello, Wasm after!

Configure Docker for Wasm

To run Wasm with Docker, first, make sure you have Docker Desktop v4.21.0 or later.

Make sure Docker Desktop has containerd and Wasm enabled. Go to Settings => Features in development:

  • Check: Use containerd for pulling and storing images
  • Check: Enable Wasm

Docker containerd

Run the Wasm module in Docker

We’re now ready to wrap the Wasm module into an OCI image.

Create a Dockerfile:

FROM scratch
COPY ./target/wasm32-wasi/debug/hello-wasm.wasm /hello-wasm.wasm
ENTRYPOINT [ "/hello-wasm.wasm" ]

Note, it is based on scratch (i.e. empty) image but this is not a problem, the Wasm module is self-contained.

Build the image:

docker build -t meteatamel/hello-wasm:0.1 .

Run the image locally in Docker with wasmedge:

docker run --runtime=io.containerd.wasmedge.v1 meteatamel/hello-wasm:0.1

Hello, Wasm before!
Hello, Wasm after!

Or with wasmtime:

docker run --runtime=io.containerd.wasmtime.v1 meteatamel/hello-wasm:0.1

Hello, Wasm before!
Hello, Wasm after!

As it’s running, you can see that it’s just a regular container:

docker ps

CONTAINER ID   IMAGE                       COMMAND             CREATED         STATUS         PORTS     NAMES
1d2e3f09a9c5   meteatamel/hello-wasm:0.1   "hello-wasm.wasm"

You can push the image to Docker Hub as usual:

docker image push meteatamel/hello-wasm:0.1

DockerHubWasm


In this blog post, I explained how Docker recently announced experimental support for running Wasm modules. I also showed how to run a Wasm module in Docker using the wasmedge runtime..

Feel free to check out my previous blog posts and GitHub repo on Wasm and reach out to me on Twitter @meteatamel for questions:


See also