What Is WebAssembly and Docker + Wasm? An in-depth look.

On October 24th, 2022, we received some pretty big news: Docker got official support for running WebAssembly applications. But if you're unfamiliar with WebAssembly (also called Wasm), this might leave you confused. Why is this big news? What is Wasm? Why is it so important for Docker?

Want the short answer? Wasm + Docker will really put the micro into microservices. It will let you launch microservices in the cloud with light speed. They will start incredibly fast, much faster than regular containers. They will even compute stuff slightly faster than regular containers can. And they'll be much smaller in size, and consume much fewer resources. A win on all fronts.

Does this mean they'll replace containers? No. But why? Well, let's dig into the long answer.

Check out this video to learn more about Wasm

From JavaScript to WebAssembly

First, we'll have a look at this WebAssembly thing: what it is and why it was needed. It's actually quite incredible technology. And it opens the door for a lot of possibilities. But to understand it we need to go back in time. A time when web pages looked like this:


Yep. That's all there was to it. A bunch of text, and only text, in a web browser. Sure, it supported formatting, making text bold, italic, smaller, larger. And the killer feature: the ability to add a link on top of some text.

At that time though, this was mind-blowing stuff! A virtually endless source of information, available to any computer connected to the Internet. Text alone did a great job at delivering information.

But, as we know, the Web evolved. Soon, you could also add images to a web page. After a while, you could also add animated graphics (known as animated GIFs). But even with all this beautification, the web pages were still static. That is, once you loaded a web page, it stayed the same no matter what you did. And people on the Internet started to feel the need for smarter, dynamic websites.

Think about Gmail. You leave the website open. Someone sends you an email and you immediately get notified. The new email automatically appears on your web page. No need to reload the website, or anything like that. That's a dynamic web page. And how is that possible? Mostly, with the help of JavaScript.

The early websites were, in a sense, dumb. All that the browser could do was download some text and images and show them to you. But now, think of a complex, modern website. You can go to a trading platform and see the price of the EUR/USD pair. You get a chart displayed on your screen.

You see how the price has evolved in the last hour. The price is updated every second. You click a button and you instantly buy EUR with the USD in your account. The page changes constantly, and you can do stuff without reloading or going to another page. You're effectively running an application directly on your web page.

JavaScript made web pages smart. Now websites can actually run code that responds to what the user is doing. That's basically what JavaScript is: code that can be executed on a web page.

Phew! Long story. But it makes an excellent point for WebAssembly. Because, Wasm… is almost the same thing: code that can be included in a web page. Weird, isn't it? What's the point? We already have JavaScript. Our web pages are smart, doing complex stuff. Why would we need yet another way to run code inside a web browser?

What's the Problem with JavaScript?

Think about this term: "http". You probably saw it a thousand times, especially if you're a long time Internet user. The web pages are transmitted over this HTTP thing, from server, to user. And HTTP stands for Hypertext Transfer Protocol. If we remember how web pages looked like back in the day

it all makes sense. This protocol was literally designed to transfer text. But, as we know, future web browsers were also able to receive images, sound, video. So the HTTP protocol evolved a lot. But everything was added on top of its old foundation.

This is pretty much what happened with JavaScript. When it was first built, it wasn't meant to run super complex applications in the web browser. At first, it was simple. A coding language that could run safely in a web browser. It shouldn't have been able to do much, by design. You wouldn't want JavaScript on a web page to be able to delete files on your computer. So JavaScript was meant to respond to simple user interactions and run simple code in a web browser. But user expectations grew and grew, so JavaScript was extended more and more.

So at its core, JavaScript is not a high-performance, all-purpose coding language. It's not a language meant to run processor-intensive applications. You wouldn't want your video editing software to be built in JavaScript. That's why such apps are usually written in C or C++. Those languages are very efficient, code runs fast, and it's able to interact with hardware much more directly.

So how is WebAssembly different? Let's see

How WebAssembly Works

You can use many programming languages to build Wasm applications. Some examples are: C, C++, C#, Rust, Go, Swift, Ruby, and many others. But how does this work?

Let's start with an easy to understand example. Let's say you wrote this piece of code in C:

int factorial(int n) {
  if (n == 0)
    return 1;
  else
    return n * factorial(n-1);
}

(Source: https://en.wikipedia.org/wiki/WebAssembly)

Can we send this code straight to the web browser? No, we can't. A browser does not know how to execute C code, directly. So this has to go through some transformations. We need to compile this C code into WebAssembly. After compilation, our C code will be transformed to this:

(Source: https://en.wikipedia.org/wiki/WebAssembly)

We have the C source code on the left. And this is transformed to WebAssembly Text format (WAT code) we can see in the middle. It's basically a translation that the compiler does. But both of these formats, C source code and WebAssembly text format, are meant for humans, not computers. That is, this source code is easy to read and understand by programmers. And it's easy to edit. However, computers do not execute this text. This is not the end result we are interested in. An extra step is needed to get the WebAssembly app we want to run in our browser. So the compiler creates the third thing we see on the right: the WebAssembly binary format. Simply put, this is the actual executable WASM code. This is the thing that a web browser can actually run. So we end up with a file we could call app.wasm and this contains our WebAssembly application. The executable code in this file is also called bytecode.

The numbers you see on the screen are hexadecimal. For example, on the fourth line we can see:

0A 17 01

0A in hex corresponds to the number 10 in decimal format and 00001010 in binary. 17 in hex corresponds to 23 in decimal and 00010111 in binary. Finally, 01 in hex corresponds to 1 in decimal and 00000001 in binary. A set of 8 bits of zeroes and ones is called a byte. So if we take these three bytes represented in hex:

0A 17 01

when converted to binary, they look like this:

00001010 00010111 00000001

And this is what computers love to work with, zeroes and ones. And we get a slight clue why this is called byte…code. It's literally code… in bytes. But bytecode has very interesting properties. And these properties make it a very useful format for Docker as well, not just web browsers.

What is WebAssembly Bytecode?

Think about an application like Notepad. When you run Notepad.exe, it basically sends instructions directly to your CPU (processor). Technically, the instructions can be monitored or restricted by the operating system. For example, Notepad cannot open a file protected by your security policies. And it can't reset your computer, directly. But it still has a lot of power and freedom. As an example, it can open your unprotected personal files. Sure, it does that only when you instruct it to do so. But Notepad is a trusted app from Microsoft. It won't have any malicious code that will start to delete your personal files. But technically, it could do that, behind your back, if it was programmed to do it.

Now think about WebAssembly. Any website on the Internet can basically run random code on our computers. Can we have the same level of trust we have in Notepad? Absolutely not. So we need to restrict this code in a very secure, isolated box.

Now if we write some C++ code and build an application, our text code gets transformed into machine code when compiled. Machine code is a collection of instructions for the CPU. Basically, instructions that the CPU can directly understand and execute. But, remember, our Wasm app has not been compiled into machine code. It was turned into bytecode instead. What is the difference?

Bytecode is not meant for the CPU, at least, not directly. Instead, bytecode is meant for a virtual machine. Wait… what? Is this supposed to run into something like VirtualBox? Well, no, that would be overkill. Bytecode instead runs into what is called a virtual stack machine. You can think of this like a microscopic virtual machine. It's super small and super simple. But it does its job well. And two of the things it does give WebAssembly some very useful properties. It:

  1. Isolates untrusted code.
  2. Translates bytecode into machine code.

1. The advantage of isolation is easy to understand. When you get some app.wasm from a website, you can run code without worrying too much. That WebAssembly code will run in a super small, well-isolated virtual machine. So even if the code is "bad" it shouldn't be able to do damage outside of its little box.

2. But you might be wondering about the second part. Bytecode is translated into machine code. What's so cool about that? First of all, let's see what actually happens. You visit a website. You get some bytecode encoded in a file like app.wasm. This is then passed to that virtual stack machine. The virtual stack machine understands the bytecode inside. But at the end of the day, it's still the CPU that has to execute that code. So the virtual machine translates bytecode into machine code that the CPU can understand.

Ok, it's necessary to transform bytecode, otherwise the CPU wouldn't be able to execute it. But, why is this an advantage? Wouldn't it be better if app.wasm would have machine code inside? So we skip this translation part, from bytecode to machine code? Well, no. Let's see why.

The Same Bytecode Can Run Anywhere

Computers are different. They run different operating systems, and use different CPU architectures. You cannot take a program.exe file from Windows and run it on Linux. The program has to be recompiled for Linux specifically. And you cannot take a program compiled for a desktop CPU and run it on a mobile CPU. As in, you cannot run a program compiled for x64 architecture and run it on an arm64 architecture. But bytecode and virtual stack machines solve these problems.

The virtual stack machine is almost like a super small virtual computer. A tiny machine, simulated in software. So it does not matter what operating system we have, or what physical processor. The virtual computer can be the same everywhere. This gives us a stable target where we can run our bytecode. So the same bytecode in some app.wasm file will run on Windows, on Linux, on macOS. It will run on x64 processors. It will run on arm64 processors.

Long story short: we can write code once. Then we can compile code once. And this code will run everywhere. No need to rewrite code or recompile it for different platforms. Does this seem familiar to you? It's basically what developers love about Docker containers. It's the same there. Developers write code once, they build containers once, and these can run everywhere. The same container will run on multiple operating systems. Now we get the first glimpse why Docker wants to support running WebAssembly.

It might seem confusing how something meant for the web browser is now being adopted by Docker too. But for WebAssembly bytecode to run, all it needs is a virtual stack machine. Remember, the virtual stack machine is just something simulated in software. It's basically a special type of program, simulating a super simple computer. So Docker can include such a software-simulated machine too. And bytecode can run anywhere. So just like it can run in the web browser, it can run under Docker too, and any other place.

But the advantages of WebAssembly + Docker just begin here. Wasm has many more properties that make it an almost perfect match with Docker. Let's first look at some general advantages of WebAssembly. After we understand these generic ones, we can dive into the Docker-specific advantages too.

What Are the Advantages of WebAssembly?

Well, from the get-go

1. WebAssembly / Wasm runs fast!

It runs much faster than JavaScript. Does this mean we don't need JavaScript anymore? Not really. Some functionality on a website does not need high performance. Like when you shop online. You click on a laptop you want to buy, then you click "Add to cart". JavaScript is great to implement that function. Performance does not matter and code is easy to write. But now think about face-recognition software. That's very intensive stuff for the computer processor. Imagine you're in a video conference. Your webcam is filming you and the web browser has to constantly track your face as you move around. If JavaScript is able to detect your face 2 times per second, Wasm could be able to do it 16-18 times per second. So in one scenario you'd be seeing a video of 1-2 FPS (frames per second), with JavaScript. That would be horrible to watch by people you're in a meeting with. In the second scenario, you'd get 16-18 FPS, with Wasm, which is acceptable quality.

The second advantage is that

2. WebAssembly / Wasm is secure by default.

Just think about it. Any website can push some code to your web browser. Any untrusted website you visit on the Internet. And your browser will happily run that Wasm code. So people considered this, and security was a main concern from the start.

As we learned in the previous sections, Wasm bytecode runs in a heavily isolated virtual stack machine. So code running there does not interact directly with the rest of the system.

Let's remember what happens when someone visits a website that wants to run some app.wasm file on a computer:

  1. User visits a website.
  2. The website sends them some app.wasm file. This contains the bytecode meant to be executed.
  3. The browser sends this bytecode to its virtual stack machine.
  4. The virtual stack machine begins to interpret the bytecode. And it translates that bytecode into actual instructions that your CPU (processor) can run.

So security is achieved multiple ways here.

  • First of all, the .wasm file doesn't have instructions that go directly to the CPU, telling it: "Hey do this, or do that."
  • Basically, the .wasm file has to pass through a sort of translation layer first, before reaching the CPU. This translation layer is the virtual stack machine. So it goes from .wasm bytecode -> to virtual stack machine -> then CPU. The virtual stack machine sitting in the middle, can prevent any bad instructions reaching the CPU. So even if the .wasm file could have some nasty code to delete some files, the virtual machine will say "No no, this is not allowed."
  • The virtual stack machine severely limits what the Wasm application "can see." Basically, for the Wasm code, its whole world is inside that virtual stack machine. It cannot see stuff that exists outside. For example, it cannot see your files, cannot access computer memory outside the virtual stack machine, and so on. You can actually pass files to Wasm code, if you want to. But that is done with some clever tricks. Wasm code, by default, cannot break out of its virtual box. But we can get external data, and feed it to that small box where WebAssembly code is running.

WebAssembly is built from the ground up with security in mind. The instructions that can be included in a Wasm app are limited to the bare minimum required. The virtual stack machine imposes some limits too. Protections are added against memory exploits. If weird stuff happens during Wasm code execution, the program is immediately terminated. And so on. This page includes more details about Wasm security.

The third advantage is that

3. WebAssembly / Wasm is Portable

Simply put, your app.wasm bytecode will run anywhere you can get a virtual stack machine running. And it will work the same way, everywhere. Write once, build once, run everywhere!

Ever went through this scenario? You had an app you loved on Windows. But you tried to find the same app for Linux or macOS. And you couldn't find it. Just imagine. Wasm could potentially eliminate this problem. At least for small-purpose, simpler applications now. But in the future, who knows? Even though Wasm is currently aimed at running small programs, it has the ability to run complex apps too. It's just tricky now, but this might change as time goes by. You can see an example here of a full-blown DOOM game running completely in the browser as a WebAssembly application.

And if you're a developer, you know this pain even better. You want to build your app for Windows 10, Windows 11, Ubuntu 20.04, Ubuntu 22.04, MacOS… and who knows what else. You want everyone to be able to run it. But it's a nightmare to support so many platforms. Well, Wasm apps don't have this issue.

The fourth advantage is that

4. You Can Build WebAssembly Apps in Almost Any Language

Just imagine you're an expert developer in the C language. You have 10 years of experience. But if you wanted to run some code in a browser, you were out of luck. You had to learn JavaScript. So you basically had to get back to the start line. You didn't know all the tricks, all the optimizations. You became a student, once again. What does WebAssembly change? Everything.

Remember this example?

Source: https://en.wikipedia.org/wiki/WebAssembly

If you have some cool C code laying around, you can now easily port it to run in a browser. Or maybe you have some cool idea about a website. Well, you can code your important functions in C, instead of JavaScript.

The same story is true if you favor C++, Rust, Go, Swift, Ruby, and many other languages. You can use the one you're an expert in. This will lead to much better code anyway. And you don't have to relearn stuff.

This is why WebAssembly / Wasm is called a compilation target. It's not your C code or Ruby code that runs in your browser. Rather, your C code, or whatever you like to use, is compiled into WebAssembly bytecode. And that bytecode runs in the browser.

So you are free to use whatever language you like. As long as it lets you compile into Wasm bytecode, you're good to go. More and more languages will support Wasm as a compilation target.

Ok. So we saw some generic Wasm advantages. We can already kind of see why this fits so well with Docker. Like, who doesn't want apps that are secure by default, running in their Docker or Kubernetes infrastructures?

But Wasm has even more benefits. We'll look at the ones that make it a perfect partner for Docker.

But first, let's clear up what might be a big mystery at this point.

What Is Docker + Wasm?

How do we run WebAssembly in Docker? Do .wasm files get inserted into containers? No. It's actually much simpler than that.

When we want to run a container, we first download a container image. And that image helps Docker build a small filesystem, with a bunch of files and directories inside. We can think of it as a micro operating system. It's not really an operating system, but it gives some app access to everything it needs. Now, Docker can start that app inside a container. And the app gets access to the filesystem. There, it can find all the libraries and extra programs it might need.

For WebAssembly under Docker, things are a bit simpler. Let's remember, what does a file like app.wasm contain? Bytecode. Where can this bytecode run? In a virtual stack machine, basically a virtual computer simulated in software. So what does Docker need here? It needs a virtual stack machine, some software that can simulate it. In this context, it's called a runtime. And Docker currently uses a runtime called WasmEdge.

What now? Well, things are super easy. Docker can just take an app.wasm file and run it in that runtime. That's all there is to it. We can see how the complexity is reduced here. There are no more filesystems with hundreds of files and directories inside. All Docker has to do is run the bytecode in some small .wasm file. Bam! Job done!

You can read this to find out how to test the Wasm+Docker integration. Please note that at the time of writing, this was pretty new stuff, so a lot might change in the future. You can leave a comment if you notice any changes.

Why WebAssembly / Wasm Is Perfect for Docker

Now let's get to the more specific reasons why Wasm is great for Docker (and by extension, also Kubernetes).

From Microservice to Super-Microservice

If you're a Kubernetes user, you know about the microservice model. We can use small containers for small jobs. They are created fast, they do their jobs fast. But, you know how it is. Anything can be better.

Did you ever think that containers are actually heavy? We've been conditioned to think that they are lightweight solutions. "They are small" we say, even when containers are 120MB in size. And we are actually both right, and wrong. Containers are super small. But only when we compare them to the alternative. What's the alternative? A virtual machine. The so-called VPS, virtual private server. It's so much harder to get something running on some virtual server in the cloud. We need to install and configure a lot of components. We need an operating system in that virtual machine too. And that operating system might require 1-2GB of disk space. Certainly a lot heavier than our 120MB container.

But think about an even smaller container, one with a size of 8MB. Small enough, right? But what does it have inside? Sure, it has the app we need. Some code that was compiled to do a specific job. The app itself might be 1MB in size, or even less. But that app depends on a lot of stuff. So all of these libraries and other things are stuffed inside the container as well. We basically have a mini-filesystem in there. So we get a similar issue to the one we had with virtual machines. The 8MB mini-filesystem in a container, is similar to the 1-2GB operating system in a virtual machine. We need to add those, as our app depends on a lot of stuff. But not everything in there is needed.

People have created stripped-down versions of container images, or operating systems. But we still end up with unnecessary stuff. Want a simple example? Our app might depend on a library. That library might include 100 different functions. But our app only uses 8. So 92 functions are unnecessary. But it's hard to strip down a library. We'd basically have to edit all of its code and make sure only the 8 functions are left inside. Long story short, we might have a 100KB library in our container, when what we actually need could require 5 or 10KB. Now let's compare this to WebAssembly.

WebAssembly is not exactly meant to run full-blown, complex apps in the browser. At least, not yet. But you may think "Wait a minute, you kept mentioning Wasm apps in this blog." That's true, I've referred to those as apps, for simplicity. Even something rudimentary that calculates x+y can be considered an app, if you enter 5 for x, 6 for y, and it displays "11" on your screen. But from a technical standpoint, it would be more accurate to say that we implemented a function in Wasm, not a full-blown app. A .wasm file, containing WebAssembly code, is usually called a module. And Wasm will absolutely excel at running such small functions, or algorithms focused on a single task. Especially those that require a lot of CPU power. They will run fast, and deliver results fast. But, with time, Wasm might also grow to support large, complex apps. No one can be certain. But history usually shows us the future. JavaScript was not initially meant to run complex Forex trading websites, where we can buy and sell USD and EUR. And yet, here it is today, doing exactly that.

But back to our story. Now just the fact that we mentioned WebAssembly will excel at implementing functions… We can already guess a problem we can solve. Docker included a library with 100 functions, out of which 92 were unnecessary. Well, with Wasm, it's easy to guess we can implement just the functions we need. We just program it that way. But even if we use a library, compilers could potentially optimize. For example, they might notice only 8 out of 100 functions are used by our code. When they compile our project, they could only include the 8 functions we need, in our Wasm bytecode. Furthermore, we also eliminate the need for a filesystem with multiple files and directories. We get rid of a ton of unnecessary things. From an 8MB container image, we might end up with a 0.8MB Wasm image. This image might have just a single .wasm file inside that does exactly the same thing as the larger container with multiple files. Now that's a huge improvement! And what does this lead to?

  • Wasm code run by Docker will start blazingly fast! Much faster than a container. All it has to do is run some super small dot .wasm file. No semi-complex container filesystem to initialize anymore.
  • .wasm files will be incredibly small. Much smaller than container images. When you move tens of thousands of containers around, that really adds up. You will definitely feel performance improvements with such workloads.

So in one case, we could have a container that could take 1 second to start and do something, delivering a result. In the other case, we could have Docker run a .wasm file that does the same thing. But the .wasm file is small. So it starts fast and finishes the job in a few milliseconds; basically, faster than you can click your mouse!

Improved Security

An application inside a Docker container can do millions of different things. It has a lot of power. It can execute almost any instruction it wants, at least inside that container. In the early days, containers were not even well-isolated from the rest of the system. Bad apps had an easy time breaking out of their little box, and infecting the whole server. Docker isolation and security are much better these days though.

So what does WebAssembly bring to the table? Well, from the start, Wasm bytecode does not have the same level of freedom. First of all, it runs in a much better isolated space: the virtual stack machine. Second of all, the virtual stack machine does not allow any random instruction to be executed. The instructions supported are somewhat limited to "safe ones".

So Wasm pretty much comes with security baked in, by default. Docker containers start with an application that can do anything, and then begin to impose some limits on it. Wasm limits things right from the start.

A server will potentially run thousands of Wasm apps, under Docker, or Kubernetes. With security baked right into Wasm, and better isolation, the server will be much safer.

WebAssembly Runs on Any Operating System and Any CPU

Not all servers run Linux-based operating systems. Not all of them use the x64 CPU architecture. But with WebAssembly, this does not matter. Once you've compiled your code into a .wasm file, you can run it anywhere! It will run on Linux, Windows, BSD, any OS you can think of. And it will run on any CPU architecture.

This could make life a bit easier for the Docker team as well. Remember how long it took until Docker was also available for Windows? Well, if Wasm was available for Docker years ago, it could have been Windows-compatible a lot earlier. So, as end-users, we might get features much faster.

All the Reasons Wasm + Docker Is a Perfect Match

Now let's recap all that we have learned. Let's go through a condensed list of the reasons Wasm is perfect for Docker:

  • Wasm files will be much smaller than containers.
  • Wasm code will start much faster than containers.
  • Wasm code can potentially perform slightly better than code run inside containers.
  • Wasm requires fewer resources, such as RAM, CPU time, disk space. So a server can simply run more stuff, which can lead to significantly lower costs.
  • Wasm has security baked in. Wasm apps are incredibly well isolated in their virtual stack machine.
  • Once you've compiled your code to a .wasm file, you can run it anywhere; on any operating system, any CPU architecture. Simply put, you can run Wasm on any type of server out there, any environment.

Are you excited about WebAssembly? I certainly am. I feel like it opens a lot of doors and a lot of possibilities. Nothing beats experience though. And it's only when you actually practice, that you understand more. So in our next lesson, let's go through a practical exercise where we will:

  1. Install Docker with support for Wasm.
  2. Write some simple C++ code.
  3. Learn how to compile it to a .wasm file (Wasm bytecode).
  4. Package this .wasm file into a Docker image.
  5. Run a Docker container based on this image (Use Docker to run WebAssembly/Wasm application).

Good luck with your WebAssembly + Docker projects. You'll be a pioneer working with the latest technology combo out there!