July 17, 2023

Docker Desktop and Spin for Serverless WebAssembly Apps

Matt Butcher Matt Butcher

docker docker desktop rust

Docker Desktop and Spin for Serverless WebAssembly Apps

Docker Desktop can run Fermyon Spin applications. Using containerd’s extension architecture, Docker Desktop can pull specially built container images that have Spin WebAssembly applications, and it can execute those alongside regular containers.

In this article, we’ll walk through the process of enabling these features, and then building and deploying a Spin application in Docker Desktop.

We are about to create and run a WebAssembly (Wasm) application in Docker. Docker’s Wasm workload functionality is in Beta, hence this new functionality does have a troubleshooting section on Docker’s documentation website. Similarly, this article has a troubleshooting section at the very end (be sure to refer to these sections if you encounter any errors). Let’s get started by enabling Wasm support in Docker.

Enabling Wasm Support in Docker

You will need Docker Desktop 4.21 or newer to use this feature. Please upgrade if you haven’t already done so.

As we mentioned, Wasm support is still an in-development (Beta) feature of Docker Desktop. Wasm support is disabled by default. To turn it on, open your Docker Desktop settings menu (click the gear icon in the top right corner of the navigation bar). Click Extensions from the menu on the left and ensure that boxes relating to Docker Marketplace and Docker Extensions system containers are checked (as shown in the image below). Checking these boxes enables the Features in development extension.

enable extensions

Please ensure that you press Apply and restart to save any changes.

Click on Features in development from the menu on the left, and enable the following two options:

  • Use containerd for pulling and storing images: This turns on containerd support, which is necessary for Wasm.
  • Enable Wasm: This installs the Wasm subsystem, which includes containerd shims and Spin (among other things).

Enable Wasm in the Docker Desktop Settings

Make sure you press Apply and restart to save the changes.

Now that Docker Desktop is Wasm-ready, we can build a quick and trivial application to test it out.

Creating a Simple Spin Application

We don’t need to do much for testing. For our purposes, we’ll just create a simple Rust app. If Rust is not your thing, the Spin Quickstart covers other languages like JavaScript, TypeScript, Python, and Go. Language doesn’t matter in this tutorial.

I have already installed Rust as well as the wasm32-wasi compiler target.

We can now go ahead and create our application:

$ spin new http-rust hello-docker --accept-defaults
$ cd hello-docker

Configuration the Application

Next, we open the Cargo.toml file and modify it to meet the following criteria:

  • That the spin-sdk dependency has its tag key set to v1.3.0.
  • That the wit-bindgen-rust dependency is being used with the same git and rev values as below. (Add this line to the [dependencies] section of your Cargo.toml file, if it does not already exist).
[dependencies]

// --snip--

# The Spin SDK.
spin-sdk = { git = "https://github.com/fermyon/spin", tag = "v1.3.0" }
# Crate that generates Rust Wasm bindings from a WebAssembly interface.
wit-bindgen-rust = { git = "https://github.com/bytecodealliance/wit-bindgen", rev = "cb871cfa1ee460b51eb1d144b175b9aab9c50aba" }

// --snip--

Building the Application

With the above configuration in place, we can now go ahead and build our application:

$ spin build 
Building component hello-docker with `cargo build --target wasm32-wasi --release`

// --snip--

Finished building all Spin components

The above command builds the Wasm binary. Different compiler toolchains put the Wasm modules in different places. With Rust, the Wasm binary will be here:

target/wasm32-wasi/release/hello_docker.wasm

That will be important when we create a Docker container.

Note that we didn’t edit any Rust source code. By default, spin new http-rust creates a “Hello world” program, and that is good enough for us.

If you’d like to give your app a quick test locally (before proceeding) you can use the spin up command:

$ spin up
Logging component stdio to ".spin/logs/"

Serving http://127.0.0.1:3000
Available Routes:
  hello-docker: http://127.0.0.1:3000 (wildcard)

Sending a request to the application will return a response object (with a status 200, header of foo:bar and a body containing Hello, Fermyon):

curl -i localhost:3000   
HTTP/1.1 200 OK
foo: bar

Hello, Fermyon                        

You can also give your app a quick test in Fermyon Cloud by using the spin deploy command.

Creating a Container

Docker Desktop works a little differently than other Wasm runtimes. While other runtimes (Spin included) package the Wasm binaries in OCI artifacts, Docker Desktop packages them in scratch images. This is not a big deal, but it does mean we need to create a Dockerfile and then build a special image.

In this next step, we create a new file called Dockerfile (which lives in the hello-docker directory next to spin.toml) and then populate our new Dockerfile with the following content:

FROM scratch
COPY spin.toml /spin.toml
COPY target/wasm32-wasi/release/hello_docker.wasm /target/wasm32-wasi/release/hello_docker.wasm
ENTRYPOINT ["/spin.toml"]

The content in the new Dockerfile does four things:

  1. It declares this image to be created from scratch instead of from a Linux or Windows image.
  2. It copies the spin.toml file into the empty image.
  3. It copies the hello-docker.wasm file into the empty image. Note that it copies it to a similarly named directory structure. The reason for this is the spin.toml refers to the exact path of the Wasm file.
  4. Specifies that we are running the application as an executable.

Once we have the Dockerfile written, we can build it. The command is a little lengthy and requires personalizing (replace technosophos with your own dockerhub username) before running:

$ docker buildx build --platform wasi/wasm --provenance=false -t docker.io/technosophos/hello-docker:latest .
[+] Building 0.1s (6/6) FINISHED                             docker:desktop-linux
 => [internal] load build definition from Dockerfile                         0.0s
 => => transferring dockerfile: 200B                                         0.0s
 => [internal] load .dockerignore                                            0.0s
 => => transferring context: 2B                                              0.0s
 => [internal] load build context                                            0.0s
 => => transferring context: 437B                                            0.0s
 => CACHED [1/2] COPY spin.toml /spin.toml                                   0.0s
 => CACHED [2/2] COPY target/wasm32-wasi/release/hello_docker.wasm /target/  0.0s
 => exporting to image                                                       0.0s
 => => exporting layers                                                      0.0s
 => => exporting manifest sha256:f17a2f8f5c53b95b72d9cda85fd074c7125e35d563  0.0s
 => => exporting config sha256:df98b0b3e98904542236da48ae7b44af8d5f7c40913d  0.0s
 => => naming to docker.io/technosophos/hello-docker:latest                  0.0s
 => => unpacking to docker.io/technosophos/hello-docker:latest               0.0s

In this example, we have built a container image named docker.io/technosophos/hello-docker:latest and copied our Spin app into the container.

At this point, we have a Docker image that contains our Wasm module. We can check to see if Docker sees this image:

$ docker image ls | grep hello
technosophos/hello-docker   latest    940456fb017f   2 minutes ago   2.67MB

Now we are ready to run it!

Running a Spin Wasm App in Docker Desktop

It is best to run Wasm images using the docker CLI. While it may be possible to run them through the Docker Desktop UI, it appears that not all of the necessary options are yet supported there.

To run it on the CLI, use a command like this (making sure to substitute technosophos for your username when referring to the image’s name):

$ docker run -i --runtime=io.containerd.spin.v1 --platform=wasi/wasm -p 3000:80 docker.io/technosophos/hello-docker:latest

This command should neither exit nor print any output yet. Because we used -i, anything that spin prints should log to the console.

The --runtime option tells Docker to use the io.containerd.spin.v1 executor instead of the default executor (which runs containers instead of Wasm images).

The --platform option tells Docker to expect an architecture of wasi/wasm. Otherwise it will default to the architecture of your system (e.g. linux/arm64/v8).

Finally, -p 3000:80 tells Docker to map Spin’s 80 port (inside the container) to the localhost:3000 port (outside the container) so that we can access it.

Here’s what it looks like with curl:

$ curl 127.0.0.1:3000/ 
Hello, Fermyon

We’ve now created a new Wasm serverless app, packaged it into a scratch container, and run it using Docker Desktop. At this point, you may want to go back to the code and edit it a bit to try things out. Just remember, each time you are ready to give it a test run, you will need to:

  1. Run the spin build command to build the Wasm file.
  2. Run the docker buildx command to rebuild the container image.
  3. Restart the docker run command so it picks up the latest changes.

Conclusion

Docker Desktop is good for running simple Spin applications. You will need to enable Wasm support the first time you use Docker Desktop for Wasm. Then there are just a few extra steps to do with your Spin app. Along with compiling to Wasm, you will need to copy your spin.toml and .wasm files both into a scratch container (using the usual Dockerfile format), but then you can run your app just like you would run any Docker container.

Ready to move beyond this simple example? Head over to the Spin Quickstart and start building your own app. Thanks for following along.


Troubleshooting (Optional)

Error: Unable to listen on 127.0.0.1:3000

The Unable to listen error message (when trying to run your application using spin up) is due to another process that is already listening on port 3000. Please ensure that no other applications are sharing this port before running spin up.

error[E0463]: can't find crate for `core`

If you encounter an error with the above text, when performing the spin build command, check out the Quickstart for tips on installing the wasm32-wasi target.

docker: Error response from daemon: failed to create task for container: failed to start shim: failed to resolve runtime path: runtime "io.containerd.spin.v1" binary not installed "containerd-shim-spin-v1": file does not exist: unknown.
ERRO[0000] error waiting for container:

If you have an error similar to the above console output, please verify that your system is capable of running a container in Docker Desktop without the Wasm feature enabled. For example, can you run docker run -it redis:latest successfully without Beta features enabled? This simple test ensures that the entire container initialization process of Docker Desktop is active and functioning as intended.

docker: Error response from daemon: Others("could not build spin trigger: Failed to instantiate component 'hello-docker'"): unknown.
ERRO[0000] error waiting for container: 

If you encounter the above could not build spin trigger error, please check that you have updated your Cargo.toml file as recommended in this article and that your Dockerfile content matches the example provided in this article.

error[E0433]: failed to resolve: use of undeclared crate or module `wit_bindgen_rust`

If you get the above undeclared crate or module error when building the application with spin build please ensure that your Cargo.toml file has the wit-bindgen-rust dependency configured (as recommended in this article).

curl: (52) Empty reply from server

The most common reason for the above error is not specifying the -p values correctly. Please use -p 3000:80 when using the docker run command (as shown in this article). This --publish flag tells Docker to publish the container’s port of 80, to the localhost:3000 port (outside the container) which makes the container accessible i.e. when using curl -i localhost:3000.

If you encounter any other issues please reach out on Discord or GitHub.


🔥 Recommended Posts


Quickstart Your Serveless Apps with Spin

Get Started