Building a Windows-Friendly Docker with REST API and WASM: A Step-by-Step Guide

Kacper Bąk
7 min readFeb 18, 2023

--

Photo by Dominik Lückmann on Unsplash

Docker is a powerful tool for deploying applications in a portable and consistent manner across different environments. In this article, we will go through the process of creating a Docker image that fires off both a REST API and a WebAssembly (WASM) module. The entire application will be made Windows-friendly and packaged into an installer, making it easy for anyone to use without requiring any expertise.

Preparing the Application

Before we start building the Docker image, we need to prepare the application that we want to deploy. In this case, we will create a simple REST API that serves up a “Hello World” message and a WASM module that calculates the sum of two numbers.

Creating the REST API

We will use Node.js and Express to create the REST API. First, let’s create a new directory for our project and navigate to it:

mkdir myapp
cd myapp

Next, we’ll initialize a new Node.js project:

npm init -y

This will create a package.json file in our project directory. Next, we'll install the required dependencies:

npm install express

Now, let’s create an index.js file in our project directory and add the following code:

const express = require('express');

const app = express();

app.get('/', (req, res) => {
res.send('Hello World!');
});

app.listen(3000, () => {
console.log('App listening on port 3000!');
});

This will create a simple Express app that listens on port 3000 and responds to requests with a “Hello World” message.

Creating the WASM Module

To create the WASM module, we will use Rust and the wasm-pack tool. First, make sure you have Rust wasm-pack installed on your machine. You can install Rust from rustup.rs and wasm-pack from wasm-pack.rs.

Next, create a new directory for the WASM module:

mkdir mywasm
cd mywasm

Now, let’s initialize a new Rust project:

cargo init --lib

This will create a new Rust project in our directory with a src directory and a Cargo.toml file.

Next, let’s add some Rust code that will export a function that calculates the sum of two numbers:

#[no_mangle]
pub fn add(a: i32, b: i32) -> i32 {
a + b
}

Now, let’s build the WASM module using wasm-pack:

wasm-pack build --target web

This will build the WASM module and generate a JavaScript module that can be used on a web page.

Creating the Docker Image

Now that we have our application ready, we can create a Docker image that packages everything together.

Writing the Dockerfile

First, let’s create a new file called Dockerfile in our project directory and add the following code:WORKDIR /app

FROM node:14-alpine

WORKDIR /app

COPY package.json .
RUN npm install

COPY index.js .

EXPOSE 3000
CMD ["npm", "start"]

This Dockerfile specifies that we want to use the node:14-alpine base image, sets the working directory to /app, copies the package.json file and runs npm install, copies the index.js file, exposes port 3000, and sets the command to npm start.

Next, let’s add the WASM module to the Docker image. We’ll need to modify our Dockerfile to do this:

FROM node:14-alpine

WORKDIR /app

COPY package.json .
RUN npm install

COPY index.js .

COPY --from=rustlang/rust:nightly as builder /usr/local/cargo /usr/local/cargo
RUN apk add --no-cache musl-dev
RUN cargo install wasm-pack
COPY mywasm .
RUN wasm-pack build --target web

EXPOSE 3000
CMD ["npm", "start"]

This Dockerfile specifies that we want to use the node:14-alpine base image, sets the working directory to /app, copies the package.json file and runs npm install, copies the index.js file installs Rust and wasm-pack from the rustlang/rust:nightly base image copies the mywasm the directory containing the Rust code for our WASM module, builds the WASM module using wasm-pack, exposes port 3000 and sets the command to npm start.

Building the Docker Image

Now that we have our Dockerfile, we can build the Docker image using the following command:

docker build -t myapp .

This will create a Docker image called myapp that we can use to run our application.

Running the Docker Container

To run our application in a Docker container, we can use the following command:

docker run -p 3000:3000 myapp

This will start a Docker container running our application and expose port 3000 so that we can access the REST API. To test the REST API, open a web browser and go to http://localhost:3000/.

Creating an Installer

To make our application even more user-friendly, we can create an installer that packages the Docker image and all its dependencies. For this, we can use a tool called docker-app.

First, install docker-app, next, create a new file called myapp.dockerapp.yaml in our project directory and add the following code:

name: myapp
version: 1.0.0
description: A simple REST API and WASM module

image: myapp

install:
- installer: myapp-install.sh
- script: docker run -p 3000:3000 myapp

uninstall:
- script: docker stop myapp

This YAML file specifies the name and version of our application, a description, the Docker image to use, and two scripts: one to install and one to uninstall the application.

Next, let’s create a shell script called myapp-install.sh in our project directory and add the following code:

#!/bin/bash

# Check if Docker is installed
if ! command -v docker &> /dev/null
then
echo "Docker is not installed. Please install Docker and try again."
exit
fi

# Check if the Docker image exists
if ! docker inspect myapp &> /dev/null
then
echo "The myapp Docker image does not exist. Building it now..."
docker build -t myapp .
fi

This shell script checks if Docker is installed and if the Docker image exists. If the Docker image doesn’t exist, it builds it using docker build.

Finally, let’s create the installer using the following command:

docker-app create myapp.dockerapp.yaml

This will create an installer called myapp.dockerapp in our project directory.

To run the installer, double-click on the myapp.dockerapp file. This will launch the installer, which will guide the user through the process of installing our application.

Once the installation is complete, the user can run the application by double-clicking on the application icon in their applications folder. This will start a Docker container running our application and expose port 3000 so that the user can access the REST API.

Linux executable file

To compile the Docker image and installer to an executable format, we can use a tool like docker-app-generator. docker-app-generator allows us to create a self-contained executable file that includes the Docker image and all its dependencies, as well as the docker-app tool itself.

Here are the steps to compile the Docker image and installer to an executable format:

  1. Install docker-app-generator by running the following command:
npm install -g @alexisjcarr/docker-app-generator

2. Create a new file called myapp.json in our project directory and add the following code:

{
"name": "myapp",
"version": "1.0.0",
"description": "A simple REST API and WASM module",
"image": "myapp",
"install": [
{
"installer": "myapp-install.sh",
"type": "script"
},
{
"command": "docker run -p 3000:3000 myapp",
"type": "docker"
}
],
"uninstall": [
{
"command": "docker stop myapp",
"type": "docker"
}
]
}

3. Create a shell script called myapp-install.sh in our project directory and add the following code:

#!/bin/bash

# Check if Docker is installed
if ! command -v docker &> /dev/null
then
echo "Docker is not installed. Please install Docker and try again."
exit
fi

# Check if the Docker image exists
if ! docker inspect myapp &> /dev/null
then
echo "The myapp Docker image does not exist. Building it now..."
docker build -t myapp .
fi

4. Run the following command to compile the Docker image and installer to an executable format:

docker-app-generator myapp.json

This will create an executable file called myapp in our project directory.

5. Run the following command to make the executable file executable:

chmod +x myapp

Run the following command to execute the executable file:

./myapp

This will start a Docker container running our application and expose port 3000 so that we can access the REST API.

By using docker-app-generator, we can create a self-contained executable file that includes everything needed to run our application, making it even easier for users to install and run our application on Windows.

Windows executable file

To compile the Docker image and installer to an executable format that can run on Windows, we can use a tool like pkg. pkg allows us to package our Node.js application and its dependencies into a single executable file for Windows, macOS, or Linux.

Here are the steps to compile the Docker image and installer to an executable format for Windows:

  1. Install pkg by running the following command:
npm install -g pkg

2. Create a new file called myapp.js in our project directory and add the following code:

const { execSync } = require('child_process');

// Check if Docker is installed
try {
execSync('docker --version');
} catch (e) {
console.error('Docker is not installed. Please install Docker and try again.');
process.exit(1);
}

// Check if the Docker image exists
try {
execSync('docker image inspect myapp');
} catch (e) {
console.log('The myapp Docker image does not exist. Building it now...');
execSync('docker build -t myapp .');
}

// Start the Docker container
execSync('docker run -p 3000:3000 myapp');

3. Run the following command to install the dependencies:

npm install

Run the following command to compile the Node.js application and its dependencies into a single executable file for Windows:

pkg myapp.js --targets win

This will create an executable file called myapp.exe in our project directory.

5. Copy the myapp.exe file to a Windows machine and double-click on it to start the Docker container and expose port 3000 so that we can access the REST API.

By using pkg, we can create a self-contained executable file that includes everything needed to run our application on Windows, making it even easier for users to install and run our application.

Conclusion

In this article, we’ve created a Docker container that runs a simple REST API and a WASM module. We’ve also created an installer that packages the Docker image and all its dependencies, making it easy for users to install and run our application on Windows.

Using Docker and tools like docker-app, we can create self-contained applications that are easy to deploy and run on any platform. By packaging our application as a Docker container, we can ensure that it runs consistently and reliably across different environments.

References

https://docs.docker.com.xy2401.com/app/working-with-app/

--

--