Docker + WASM
The Docker Desktop distributes with the WasmEdge Runtime embedded. That allows developers to build, share and run very lightweight containers (i.e., a scratch
empty container with only the .wasm
file without any Linux OS libraries or files) through Docker tools. Those "WASM containers" are fully OCI-compliant and hence can be managed by Docker Hub. They are cross-platform and can run on any OS / CPU Docker supports (the OS and CPU platform is wasi/wasm
). But most importantly, they are 1/10 of the size of a comparable Linux container and start up in 1/10 of the time, as the WASM containers do not need to bundle and start Linux libraries and services.
Together with Docker's capability to containerize developer and deployment environments, you can create and deploy complex applications without installing any dependencies. For example, you could setup a complete Rust and WasmEdge development environment without installing either tool on your local dev machine. You can also deploy a complex WasmEdge app that needs to connect to a MySQL database without having to install MySQL locally.
In this guide, we will cover how to:
- Create and run a Rust program
- Create and run a node.js server
- Create and deploy a database driven microservice in Rust
Prerequisite
Install Docker Desktop and turn on the containerd image store feature in your Docker Desktop settings.
Create and run a Rust program
With Docker + WASM, you can use the entire Rust toolchain in a Docker container to build the WASM bytecode application, and then publish and run the WASM application. The example Rust source code and build instructions are available here.
Build the rust example
In the project directory, run the following command to build the Rust source code into WASM and then package the WASM file into an empty container image. Notice that you do not need to install the Rust compiler toolchain here.
docker buildx build --platform wasi/wasm -t secondstate/rust-example-hello .
The Dockerfile shows how it is done. The Dockerfile has three parts. The first part sets up a Docker container for the Rust build environment.
FROM --platform=$BUILDPLATFORM rust:1.64 AS buildbase
WORKDIR /src
RUN <<EOT bash
set -ex
apt-get update
apt-get install -y \
git \
clang
rustup target add wasm32-wasi
EOT
The second part uses the Rust build environment to compile the Rust source code and generate the WASM file.
FROM buildbase AS build
COPY Cargo.toml .
COPY src ./src
# Build the WASM binary
RUN cargo build --target wasm32-wasi --release
The third part is the essential. It copies the WASM file into an empty scratch
container and then set the WASM file as the ENTRYPOINT
of the container. It is the container image rust-example-hello
built by the command in this section.
FROM scratch
ENTRYPOINT [ "hello.wasm" ]
COPY --link --from=build /src/target/wasm32-wasi/release/hello.wasm /hello.wasm
The WASM container image is only 0.5MB. It is much smaller than a natively compiled Rust program in a minimal Linux container.
Publish the rust example
To publish the WASM container image to Docker Hub, do the following.
docker push secondstate/rust-example-hello
Run the rust example
You can use the regular Docker run
command to run the WASM container application. Notice that you do need to specify the runtime
and platform
flags to tell Docker that this is a non-Linux container and requires WasmEdge to run it.
$ docker run --rm --runtime=io.containerd.wasmedge.v1 --platform=wasi/wasm secondstate/rust-example-hello:latest
Hello WasmEdge!
That's it.
Further reading for the rust example
To see more Dockerized Rust example apps for WasmEdge, check out the following.
Create and run a node.js server
WasmEdge provides a node.js compatible JavaScript runtime. You can create lightweight WASM container images that runs node.js apps. Compared with standard node.js Linux container images, the WASM images are 1/100 of the size, completely portable, and starts up in 1/10 of the time.
In this guide, the example app is an HTTP web server written in node.js. Its source code and build instructions are available here.
Build the node.js example
In the project directory, run the following command to package the WasmEdge JavaScript runtime and the JS HTTP server program into an empty container image.
docker buildx build --platform wasi/wasm -t secondstate/node-example-hello .
The Dockerfile shows how it is done. The Dockerfile has three parts. The first part sets up a Docker container for the wget
and unzip
utilities.
FROM --platform=$BUILDPLATFORM rust:1.64 AS buildbase
WORKDIR /src
RUN <<EOT bash
set -ex
apt-get update
apt-get install -y \
wget unzip
EOT
The second part uses wget
and unzip
to download and extract the WasmEdge JavaScript runtime files and the JS application files into a build container.
FROM buildbase AS build
COPY server.js .
RUN wget https://github.com/second-state/wasmedge-quickjs/releases/download/v0.5.0-alpha/wasmedge_quickjs.wasm
RUN wget https://github.com/second-state/wasmedge-quickjs/releases/download/v0.5.0-alpha/modules.zip
RUN unzip modules.zip
The third part is the essential. It copies the WasmEdge JavaScript runtime files and the JS application files into an empty scratch
container and then set the ENTRYPOINT
. It is the container image node-example-hello
built by the command in this section.
FROM scratch
ENTRYPOINT [ "wasmedge_quickjs.wasm", "server.js" ]
COPY --link --from=build /src/wasmedge_quickjs.wasm /wasmedge_quickjs.wasm
COPY --link --from=build /src/server.js /server.js
COPY --link --from=build /src/modules /modules
The WASM container image for the entire node.js app is only 1MB. It is much smaller than a standard node.js image, which is 300+MB.
Publish the node.js example
To publish the WASM container image to Docker Hub, do the following.
docker push secondstate/node-example-hello
Run and test the node.js example
You can use the regular Docker run
command to run the WASM container application. Notice that you do need to specify the runtime
and platform
flags to tell Docker that this is a non-Linux container and requires WasmEdge to run it. Since this is an HTTP server app, you also need to map the container port 8080 to host so that you can access the server from the host.
$ docker run -dp 8080:8080 --rm --runtime=io.containerd.wasmedge.v1 --platform=wasi/wasm secondstate/node-example-server:latest
listen 8080 ...
From another terminal, test the server application.
$ curl http://localhost:8080/echo -X POST -d "Hello WasmEdge"
Hello WasmEdge
That's it.
Further reading for the node.js example
Create and deploy a database driven microservice in Rust
Docker + wasm allows us to build and run WASM containers. However, in most complex applications, the WASM container is only part of the application. It needs to work together with other Linux containers in the system. The Docker compose tool is widely used to compose and manage multi-container deployments. It is installed with Docker Desktop.
In our example microservice application, there is an Nginx web server and a MySQL database. The WASM container is only for the Rust application that accesses the database and processes the HTTP requests (i.e., the application server).
For more Docker compose examples, including Linux containers + WASM containers mixed deployments, check out the awesome-compose repo.
Build the microservice example
In the project directory, run the following command to build all three containers: client
, server
and db
.
docker compose up
There is a docker-compose.yml file. It defines the 3 containers needed in this application.
services:
client:
image: nginx:alpine
ports:
- 8090:80
volumes:
- ./client:/usr/share/nginx/html
server:
image: demo-microservice
platform: wasi/wasm
build:
context: .
ports:
- 8080:8080
environment:
DATABASE_URL: mysql://root:whalehello@db:3306/mysql
RUST_BACKTRACE: full
restart: unless-stopped
runtime: io.containerd.wasmedge.v1
db:
image: mariadb:10.9
environment:
MYSQL_ROOT_PASSWORD: whalehello
- The
client
container is an Nginx web server- Linux container with mapped HTTP port and volume for the static HTML/JS files
- The
server
container is a Rust container for the business logic- The WASM container is built from Rust source code using this Dockerfile
- WASM container with mapped web service port and an environment variable for the database connection string
- The
db
container is a MySQL database- Linux container with a pre-set database password
Deploy the microservice example
Start and run all three containers in the correct order with one command.
docker compose up
Go back to Docker Desktop Dash board, you will see there're three containers running.
CRUD tests
Open another terminal, and you can use the curl
command to interact with the web service.
When the microservice receives a GET
request to the /init
endpoint, it would initialize the database with the orders
table.
curl http://localhost:8080/init
When the microservice receives a POST
request to the /create_order
endpoint, it would extract the JSON data from the POST
body and insert an Order
record into the database table. For multiple records, use the /create_orders
endpoint and POST
a JSON array of Order
objects.
curl http://localhost:8080/create_orders -X POST -d @orders.json
When the microservice receives a GET
request to the /orders
endpoint, it would get all rows from the orders
table and return the result set in a JSON array in the HTTP response.
curl http://localhost:8080/orders
When the microservice receives a POST
request to the /update_order
endpoint, it would extract the JSON data from the POST
body and update the Order
record in the database table that matches the order_id
in the input data.
curl http://localhost:8080/update_order -X POST -d @update_order.json
When the microservice receives a GET
request to the /delete_order
endpoint, it would delete the row in the orders
table that matches the id
GET
parameter.
curl http://localhost:8080/delete_order?id=2
That's it. Feel free to fork this project and use it as a template for your own lightweight microservices!
Further reading for the microservice example
To learn how Docker + WASM works under the hood, visit the containerd chapter for more details.