Blazor WASM with Caddy 2
Tags: blazor, webassembly, dotnet, caddy
Published: 2020-06-10 15:30:09

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](https://dotnet.microsoft.com/apps/aspnet/web-apps/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](https://caddyserver.com/) 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)

```bash
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`.

```caddy2
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):

```bash
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:

```bash
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! :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`:

```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`:

```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.

```bash
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! :tada:

Don't forget to try out the PWA functionality :+1:.

# 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](https://www.youtube.com/watch?v=T6pepcxEudI)), Vercel, Render, etc.
And with Docker you can host your app on any Provider supporting Docker. Pretty nice, isn't it?

**Related links:**
 - [Blazor Hosting Docs](https://docs.microsoft.com/en-us/aspnet/core/host-and-deploy/blazor/webassembly?view=aspnetcore-3.1)
 - [Caddy Docs](https://caddyserver.com/docs/)
 - [Caddy Docker Docs](https://hub.docker.com/_/caddy?tab=description)