Blazor WASM Dockerizing
Blazor
29 Articles
In this article, let's learn about Dockerizing
Blazor apps.
Note: If you have not done so already, I recommend you read the article on Blazor WASM Pre Rendering.
Table of Contents
Introduction
Containers are like packages that contain everything an application needs to run, including the application code, its dependencies, configuration files, and more. By putting all of these things together in one place, containers make it easy to run an application consistently across different environments.
To use containers, you first create an image that contains all of the necessary components. Then, you can create instances of this image, which are called containers, and run them on any machine that supports the container platform.
In this tutorial, we'll learn how to use containers to run a Blazor Server application. We'll cover how to create an image that includes all of the necessary components, and then how to use that image to create and run containers.
Before we get into things, let's cover what Docker is and a few key concepts.
What is Docker?
Docker
is a tool that helps people build, share, and run containers. Containers are like tiny packages that contain just what an
application needs to run, and they can be run on a shared operating system kernel. Because they're so lightweight, many containers can be run on a single physical machine,
making them more efficient than traditional virtual machines.
Containers
are quick to start up because they only contain what's necessary to run the application. This makes them great for scaling
on demand, because new containers can be started in just a few seconds. In contrast, traditional virtual machines take a few minutes to start up, so they're not as good for
scaling quickly.
Docker File
A Dockerfile
is like a step-by-step instruction manual that tells Docker how to create an image of your application. When you run the
docker build
command with a Dockerfile, Docker uses the instructions in the file to create an image.
Image
When you run a Dockerfile, it creates a Docker image
that contains your application. This image is built up in layers, like an onion.
Each layer can be cached to speed up the build process. Once an image is created, it cannot be changed, but it can be used as a starting point for creating new images.
Docker images can be stored in an image repository, which is like a library of images. Some examples of image repositories are Docker Hub
and Azure Container Registry
. Image repositories make it easy to share your images with others who might need them. It's kind of like
NuGet
, but for containers.
Container
When you run an image, it creates a container
. A container is like a copy of the image that's running and doing the work. You can create
many containers from a single image. To start a container, you use the docker run
command and tell Docker which image to use as the basis
for the container.
Containerising a Blazor WASM App
Challenge
Blazor WebAssembly projects require a different approach because they produce static files when published. Unlike other ASP.NET Core apps, we don't need to use the ASP.NET Core runtime to serve them. This means we don't need to use the ASP.NET Core runtime Docker image as the base for our final image. So, how are we going to serve our files? The solution is to use NGINX.
What is NGINX?
NGINX
is a free and open-source web server that can be used as a reverse proxy, load balancer, and HTTP cache. It's great at serving
static content quickly. Compared to Apache, it uses less memory and can handle up to four times the number of requests per second.
There's a Docker image available for NGINX, with several different versions to choose from. We'll be using the NGINX:Alpine
image,
which is very small - less than 5MB - and contains everything we need to serve our Blazor WebAssembly application.
Prerequisites
If you haven't used Docker
before, you'll need to install Docker Desktop
or Rancher Desktop for either Windows or Mac. Just follow the instructions provided during the setup
process and you should be up and running within a few minutes. For this tutorial, we'll be using the ilovedotnet
project repo for a Blazor WASM application.
Creating a NGINX Configuration File
To serve our Blazor WebAssembly application in our container, we need to use NGINX web server. Since our app is a Single Page Application (SPA), we need to configure NGINX
to route all requests to index.html
. To do this, we create a new file named nginx.conf
in the
project's root directory and paste the provided code.
Code Sample - NGINX Configuration File
Let's break down the above code and understand in detail
events
section defines the event loop used by NGINX. In this case, the default values are used, so the
section is empty.
http
section defines the HTTP server, including the server block and the location block.
The include mime.types
directive loads the MIME types file, which defines the file types and their
associated content types that NGINX can serve.
The types
block defines additional MIME types that NGINX should recognize. In this case, the
application/wasm
MIME type is added.
The server
block defines the virtual server. The listen directive specifies the IP address and port number
that the server should listen on. In this case, it listens on port 80
.
The index
directive specifies the default file name that should be served when a directory is requested. In
this case, index.html
is served.
The location
block specifies how NGINX should handle requests for a specific location. In this case,
requests to the root URL /
are handled. The root
directive specifies the directory where NGINX should look for files to serve. In this case, it's /user/share/nginx/html
.
The try_files
directive tells NGINX to try to serve the requested file first $uri
,
followed by any directory with the same name as the requested file $uri/
. If neither of those exists, NGINX
will serve the index.html
file. If that file doesn't exist either, NGINX returns a 404 error
=404
.
Overall, this configuration sets up a basic HTTP server
that listens on port 80
and serves files
from the /usr/share/nginx/html
, with support for the application/wasm
MIME
type. If you plan to use this in production, it is recommended to refer to the NGINX docs for all configuration options.
Creating a Docker File
Let's create a dockerfile in the project's root directory with the following code.
Code Sample - Docker File
Let's break down the above code and understand in detail
FROM mcr.microsoft.com/dotnet/sdk:8.0-alpine AS build-env
specifies the base image for the build environment.
The AS
keyword is used to create a named build-env which is an Alpine-based image that includes the .NET Core
SDK.
RUN apk add nodejs
and RUN apk add npm
install
Node.js and NPM package manager using Alpine's package manager apk.
WORKDIR /app
sets the working directory inside the container to /app
.
COPY . ./
copies all files from the host machine to the container's /app
directory.
RUN npm --prefix CommonComponents install
installs the required packages and dependencies of the Node.js application found
in the CommonComponents directory, which is a subdirectory inside /app
. The --prefix
option specifies the installation path of the packages.
RUN dotnet publish "Web/Web.csproj" -c Release -o output
builds and publishes the .NET Core application found
in the Web/Web.csproj
file. The -c option specifies the build configuration and -o option specifies the output
directory.
FROM nginx:alpine
specifies the base image for the final image, which is Nginx in this case.
WORKDIR /user/share/nginx/html
sets the working directory inside the container to
/user/share/nginx/html
.
COPY --from=build-env /app/output/wwwroot .
copies the published output of the .NET Core application from
the previous build stage to the current image's working directory. The --from
option specifies the build
stage to copy from.
COPY Web/nginx.conf /etc/nginx/nginx.conf
copies the Nginx configuration file found in the Web directory to
the /etc/nginx
directory inside the container.
EXPOSE 80
exposes port 80
of the container
to the host machine.
Overall, this Dockerfile sets up an environment to build a .NET Core application and an Nginx web server. The build environment installs Node.js and NPM, copies the .NET Core application and its dependencies, builds and publishes the .NET Core application, and outputs the published files to a designated folder. Then, in the final image, Nginx is installed and configured to serve the published files, and port 80 is exposed for access.
A word of caution!!. The docker file created above doesnt work directly with docker build
command. There exists some path issues. So
I have decided to use docker compose to up and run with above docker file.
Creating a Docker Compose File
Docker Compose is a tool for defining and running multi-container Docker applications. It allows you to define a set of containers and their configurations in a single file, called a Docker Compose file. The Docker Compose file is written in YAML syntax and contains information about each container, such as its image, environment variables, network settings, and volumes.
By defining all the containers in a single file, Docker Compose allows you to easily spin up and manage complex applications with multiple containers, such as web servers, databases, and messaging systems. You can start all the containers in the application with a single command, and Docker Compose will ensure that they are started in the correct order and with the correct configuration.
Create the following docker-compose.yml
at the root of the Solution
folder.
Code Sample - Docker Compose File
This Docker Compose file sets up a service named ilovedotnet
that runs a pre-built Docker image or builds an image from a Dockerfile,
sets environment variables, and maps the container's port 80 to the host machine's port 8080.
Up and Run via Docker
Time to see a demo. We have our docker-compose.yml
setup at solution folder level
and
Dockerfile
and nginx.conf
setup at project folder level
.
All we need to do now is to run command docker compose up
from the solution folder level. This will build the image and start the
container. You can clone the ilovedotnet repo and execute the above command.
After running the command, open a web browser and navigate to http://localhost:8080
. The application should load successfully as shown
below.
Summary
This article covered the challenges that come with running a Blazor WebAssembly application in a container. We created an image for our application that utilizes NGINX to serve the static content generated by Blazor WebAssembly applications. Finally, we verified that everything was working as expected by starting a container using our docker compose setup.