DEV Community

loading...
Cover image for Blazor WASM with Caddy 2

Blazor WASM with Caddy 2

pvormste profile image Patric Vormstein ・4 min read

Are you also so excited about new technologies like me? Are you a fan of .NET? Bonus: Do you dislike to write frontend code because of JavaScript?

Then welcome to the club!

Blazor is Microsoft's approach to bring .NET to the browsers and enable developers to use a single stack for backend and frontend development.
There are 2 possible hosting models for Blazor:

  • as server solution where the state of the client is saved on the server and changes are transported via SignalR (WebSockets)
  • as WASM solution where the .NET Runtime and the frontend application are packed as WASM and retrieved by user's browser (yes, the client will need to download up to 7 MB of data initially...)

In this article I want to show you how you can statically host you Blazor WASM app with Caddy 2.
Caddy 2 is a web server / proxy solution similar to Nginx. It is written in Go and therefore available for all major OS.

So let's get started!

Local environment

I will assume that you have basic knowledge of .NET Core and also having Caddy installed on your system. But I won't touch any C# code because the Blazor template is already a working app!

So first we will setup the project. For the Blazor project, I will use the --pwa flag because it would be really cool to be able to use the app as desktop app later! (Yes that's all what we need for PWA support)

dotnet new sln -n BlazorWASM
dotnet new blazorwasm -o BlazorWASM.Client --pwa
dotnet sln add BlazorWASM.Client/BlazorWASM.Client.csproj

With the basic setup inplace we now want to add a so called Caddyfile which will configure Caddy for us.
Put it into ./BlazorWASM.Client/wwwroot/Caddyfile.

localhost

try_files {path} /index.html
file_server

The content is a short story: We are telling Caddy that our page should live under the localhost hostname. It should serve the files as a file server and when a file is not found it should redirect the request to the index.html.

Examples:
https://localhost/css/app.css => works!
https://localhost/where-is-my-beer => redirect to index.html

Oh, I forgot to mention that Caddy will automatically setup https for us! Awesome!

In the next step we need to publish our Blazor app (to a folder):

cd BlazorWASM.Client
dotnet publish -o bin/Publish BlazorWASM.Client.csproj

I'm using the folder bin/Publish here, but actually this doesn't matter - put it where you want.

Now head to the publish folder and start Caddy:

cd bin/Publish/wwwroot
caddy run

If you are wondering whats happening here: Caddy picks up our Caddyfile we have written before and sets up https for our site. It may be the case that Caddy asks for your system password to install the certificates.

When Caddy has started you can now head to https://localhost in your browser of choice (except IE, because IE doesn't support WASM and you really shouldn't use IE anymore ...).

And tada! 🎉
You are now seeing the Blazor WASM app statically hosted via Caddy.

Docker

So far so good! Let's take this further and try to host the Blazor app in a docker container. With Caddy of course.

For that we need to change our Caddyfile:

{
    auto_https disable_redirects
}

localhost:80 {
    try_files {path} /index.html
    file_server
}

So, why are we changing the Caddyfile? We need to turn off the automatic https configuration because we don't have a domain and we will run the container locally. If we wouldn't to this, the browser won't trust the certificate and complain about it.

auto_https is a new feature in Caddy 2.1 and won't work with Caddy 2.0 or below. (It is possible for 2.0 with a json config, but this is out of scope)

Now create the Dockerfile ./BlazorWASM.Client/Dockerfile:

FROM mcr.microsoft.com/dotnet/core/sdk:3.1 AS builder
WORKDIR /caddy
RUN curl -L https://github.com/caddyserver/caddy/releases/download/v2.1.0-beta.1/caddy_2.1.0-beta.1_linux_amd64.tar.gz -o ./caddy_2.1.0-beta.1_linux_amd64.tar.gz
RUN tar xfvz caddy_2.1.0-beta.1_linux_amd64.tar.gz
WORKDIR /build
COPY . .
RUN dotnet publish -o bin/Publish

FROM alpine:latest
ENV XDG_CONFIG_HOME=/config
ENV XDG_DATA_HOME=/data
VOLUME /config
VOLUME /data
WORKDIR /site
COPY --from=builder /caddy/caddy /bin/caddy
COPY --from=builder /build/bin/Publish/wwwroot/ .
ENTRYPOINT ["caddy", "run"]

We are using a multi stage Dockerfile here. The first stage will download Caddy 2.1 and publish our Blazor app.
The second stage copies all needed data over from the builder container and sets up some variables needed by Caddy.

Add a ./BlazorWASM.Client/.dockerignore file and ignore build related directories - we don't need them to be copied over:

# .dockerignore
bin/
obj/

Time to test it! Build the container and create a volume for the data Caddy needs to persist.
Then start it - we will use a port mapping from 9000 to 80.

docker build -t blazorwasm . 
docker volume create caddy-data
docker run -p 9000:80 -v caddy-data:/data blazorwasm

Open your browser again and visit http://localhost:9000.
Hello Blazor in Docker! 🎉

Don't forget to try out the PWA functionality 👍.

Going further

I hope you can see how powerful this setup can be - you can host you Blazor app on any static hosting provider, e.g. Azure Storage (Video), Vercel, Render, etc.
And with Docker you can host your app on any Provider supporting Docker. Pretty nice, isn't it?

Related links:

Discussion

pic
Editor guide
Collapse
mosthated profile image
MostHated

Thanks for the tutorial. It would be super helpful to have it in a git repo. It always helps to have a fully working example to reference.