Building, Deploying and Observing WASM Apps

Home Blog Building, Deploying and Observing WASM Apps

This post gives an overview of how to build applications using the updated Docker + WASM technical preview, along with some observability best practices.

What is WASM?

WebAssembly, also known as WASM, serves as a binary instruction format for a stack-based virtual machine. This means it is a new type of code and is specifically designed to run in modern web browsers. This is made possible because WASM is architected as a portable compilation target for programming languages. This design enables deployment on the web for any type of client and server applications.

In principle, WASM is a low-level language, but it is designed to be easy to compile to from other languages. Therefore, an application developer can write programs in languages such as C, C++, Rust, etc. and compile them into a WASM target to be run in the browser. 

The ultimate goal of WASM is to help application developers package and deploy their apps for deployment. Where have we heard this before – with Docker and containers. Since there is a large overlap between the aims of WASM and Docker, it is a natural choice for them to work together. Enter, Docker + WASM technical preview. Announced in October 2022, Docker could be used to package, deploy, and manage WASM applications, just like any other type of containerized application. Fast forward six months, the team announced a refreshed integration. Let us take a look at some of the details about this effort. 

Why use WASM + Docker Technical Preview?

The WASM+Docker Technical Preview project was started to explore the potential of running WebAssembly (WASM) applications inside Docker containers. WASM and Docker, both of which are aimed squarely at broadening the horizons for application developers, share some underpinnings which makes their integration very valuable. The WASM+Docker Technical Preview project combines the benefits of WASM and Docker to create a powerful platform for modernized web applications. The salient features promised to developers are three-fold: 

  1. High-performance: WASM code can run with near-native performance, which makes it possible to create web applications that are more responsive and interactive than ever before.
  2. Portability: WASM code can be compiled to run on any platform that supports Docker, which makes it easy to deploy applications to different environments.
  3. Security: WASM code is sandboxed, which means that it cannot access the host system or other containers. This makes it a secure platform for running web applications.

To quote Jake Levirne, Head of Product at Docker — “Wasm is complementary to Docker — in whatever way developers choose to architect and implement parts of their application, Docker will be there to support their development experience”.

A second iteration of WASM+Docker was announced in March 2023. The features released in this iteration made it easier for developers to write, debug, configure, and maintain WASM apps, especially running as containerized workloads using Docker. The most significant update was the support made available for new WASM runtimes. 

Which WASM Runtime?

A WebAssembly (WASM) runtime is a software environment that executes WASM code. It provides the necessary infrastructure for loading, compiling, and running WASM modules. WASM runtimes are typically implemented as libraries or executables that can be embedded in other applications. They can also be used as standalone tools for running WASM modules. There are a number of different WASM runtimes available, each with its own specific traits. Some of the most popular ones are: 

  • Wasmtime is a high-performance WASM runtime that is designed for use in browsers and servers. It is written in Rust and is available as a standalone library or as a Docker image. It supports Ahead-of-Time (AOT) compilation.
  • WASMEdge is a lightweight WASM runtime, which is designed for use in embedded devices and IoT applications. It is also available as a standalone library or as a Docker image. It is written in C. 
  • Subtle is a secure WASM runtime that is designed for use in applications that require high levels of security. It is written in Rust and can be consumed as a standalone library or as a Docker image.
  • Emscripten is a compiler that can convert C and C++ code to WASM. While it is not a runtime in itself, it can be used to create WASM applications that can be run on a variety of platforms.

Why Should I Use WASM?

There are several use cases for a technology like WASM because of its broad range of applications. Here are some use cases that best illustrate the need for the technology:

  1. Migrating from desktop-only to desktop and browser-based applications: WASM can be used to port desktop applications to the web, allowing them to be run in a browser. This can be a great way to reach a wider audience and make your applications more accessible.
  2. Modernizing older apps written in Silverlight: WASM can be used to modernize older apps that were written in Silverlight. Silverlight is a discontinued technology, but WASM can be used to run Silverlight apps in a modern browser.
  3. Backward compatibility with older platforms: WASM can be used to provide backward compatibility with older platforms. For example, you could use WASM to run a native code application on a browser that does not support that native code.
  4. Progressive web apps: WASM can be used to create progressive web apps (PWAs). PWAs are web applications that can be installed on a user’s device and run like a native app. WASM can be used to improve the performance and functionality of PWAs.
  5. Mobile apps: WASM can be used to create mobile apps. This can be a great way to create cross-platform apps that can run on both iOS and Android devices.

There are a few recurring themes around WASM that make it popular among web application developers today. The first is the promise of increased performance. Who doesn’t appreciate fast and responsive applications? The ability to design performant web applications using a broad swathe of programming languages is an enticing prospect for potential WASM users. In comparison to Linux containers, WASM applications consume less resources. This is a result of the design where code is compiled as WASM binaries that require far less resources than comparable linux binaries. Also, WASM applications are limited to being single-threaded compared to equivalent multi-threaded applications running on linux, resulting in far less CPU usage. Due to their ability to run in sandboxed environments, WASM is a good choice for security requirements. These are the chief reasons why WASM is rising in popularity among developers. 

Caveats When Using WASM

All that being said, WASM isn’t perfect, and the Docker + WASM Technical preview is in an early stage. There are several things to keep in mind when trying the Docker + WASM technical preview. The common issues that people are encountering are: 

  1. Not all containers are created equal. If you try to run a WASM application alongside a traditional Linux container, there is a chance that the two applications will not be compatible. This is because WASM applications are sandboxed, which means that they do not have access to the same resources as traditional Linux containers.
  2. Since WASM applications are still under development, there is a risk that they may contain security vulnerabilities that have not been discovered yet. This concern is amplified further because WASM applications can be run on a wider range of platforms and devices than traditional Linux containers. 

Tutorial: Docker + WASM Technical Preview

Now that we’ve seen what is good and what’s not good about the WASM + Docker Technical preview, let’s get into a quick tutorial to see where to begin. This tutorial is written for a TypeScript application. As with any new technology, there are a few prerequisites for this one too. They are: 

  1. the latest Docker Desktop installed. 
  2. A TypeScript compiler
  3. A WASM runtime of your choice

Outline for the tutorial:

  • Create a new TypeScript project. 
  • Write a simple TypeScript application. 
  • Build the TypeScript application to a WASM module. 
  • Create a Dockerfile. 
  • Build the Docker image. 
  • Run the Docker image.

Let’s take a look at these steps in detail:

First, create a new TypeScript project

mkdir ts-wasm-example
cd ts-wasm-example
vi app.ts

Then save the following into the `app.ts` file, hit `i` to insert into the file and paste the following:

const sayHello = () => {
  console.log("Hello, world!");
};

export { sayHello };

Tip: To save and close the newly created file press escape if still in insert mode, then press `:wq`

Build the TypeScript application into a WASM module

This will create a ts-wasm-example.wasm file in the current directory. Next, write a Dockerfile which will build an image using this Dockerfile. It will create an extremely minimal container containing only the “my-wasm-app.wasm” file. When the container is run, it will automatically execute the Wasm application as its main process. 

The “scratch” image is a special minimalistic empty image that is often used as a starting point for creating small, self-contained containers. The command in the next line copies the file named “my-wasm-app.wasm” from the build context (the directory where the Dockerfile is located) into the root directory of the container. Then, the CMD command in the third line sets the default command to run the “my-wasm-app.wasm” file, which is the WebAssembly application copied in the previous step.

FROM scratch
COPY my-wasm-app.wasm /
CMD ["/my-wasm-app.wasm"]

Next, build the Docker image. 

docker build -t my-wasm-app .

Once built successfully, run the image. 

This will start a Docker container that will run the WASM module. You should see the following output in the console:

Going through these steps will help you verify that a basic setup is available for working with the Docker _ WASM Technical Preview. 

Extending To Include Observability

Next, we will see how to add observability capabilities to this application. First, install the npm packages corresponding to the opentelemetry capabilities. 

npm install @opentelemetry/api @opentelemetry/tracing @opentelemetry/metrics

Open the TypeScript app created in the preview step and modify it to include the OpenTelemetry provider. 

Tip: To use vim again simply `vi app.ts` then `gg` followed by `V` and `G` to select all in the file before pasting the below.

import {
  OpenTelemetry,
  Metrics,
  MetricsProvider,
  TelemetryClient,
} from "@opentelemetry/api";
import { TracerProvider } from "@opentelemetry/tracing";

const ot = new OpenTelemetry();
const metrics = new Metrics(ot);
const metricsProvider = new MetricsProvider(metrics);
const tracerProvider = new TracerProvider();

const client = new TelemetryClient(tracerProvider, metricsProvider);

client.start();
const sayHello = () => {
  console.log("Hello, world!");
};

export { sayHello };
client.stop();

This code will create a new OpenTelemetry instance and initialize the metrics and tracing providers. It will then create a new TelemetryClient and start it. Finally, it will do some work that you want to instrument. When the work is done, the TelemetryClient will be stopped. 

Once the file has been modified, go through the steps of building and running the container once again. 

docker build -t my-wasm-app .
docker run my-wasm-app

This will now let you collect metrics from the app using OpenTelemetry. 

Concluding Observations

This article should provide an understanding of Docker + WASM Technical Preview. Please remember to use the latest version of Docker Desktop. The Docker + WASM Technical Preview is constantly being updated, so it is important to ensure that the recommended version of Docker is being run. This will help to ensure that you have the latest bug fixes and security updates. Another thing to remember is that before deploying WASM applications to production, test them thoroughly. This will help identify any potential problems and ensure that the applications are compatible with Docker + WASM.

You can also apply the same principles for various programming languages and observability tools. You could integrate Lumigo for getting metrics and traces from your applications when using the Docker + WASM technical preview.