
What Is Docker?
Docker allows applications to be packaged into containers containing everything required to run them. Rather than installing an application directly onto a server, you create an image containing the application and its runtime, then use that image to create a running container.
This makes deployment much more consistent. The same Docker image can be tested on a development PC and then deployed to a Linux server, cloud platform or another machine running Docker.
For a Blazor Server application, this is particularly useful because the application runs on the server. The browser communicates with the server over a persistent connection, while the .NET application handles the processing.
The Blazor Server Application
For this example, we will assume we have a standard Blazor Server application targeting .NET 10.
The project might look something like this:
BlazorDockerDemo │ ├── Components ├── Properties ├── wwwroot ├── Program.cs ├── appsettings.json └── BlazorDockerDemo.csproj
There is nothing particularly special about the application. The objective is to leave the application itself largely unchanged and let Docker handle how it is built and deployed.
One important point is that the version of the .NET SDK used to build the application should match the target framework of the application. The official .NET container images provide both SDK images for building applications and ASP.NET Core runtime images for running them.
Creating the Dockerfile
The first step is to create a file called Dockerfile in the root of the project.
A Dockerfile contains the instructions Docker uses to build an image. For our Blazor Server application, we can use a multi-stage build.
# Build stage
FROM mcr.microsoft.com/dotnet/sdk:10.0 AS build
WORKDIR /src
COPY ["BlazorDockerDemo.csproj", "./"]
RUN dotnet restore "BlazorDockerDemo.csproj"
COPY . .
RUN dotnet publish "BlazorDockerDemo.csproj" \
-c Release \
-o /app/publish \
--no-restore
# Runtime stage
FROM mcr.microsoft.com/dotnet/aspnet:10.0 AS final
WORKDIR /app
COPY --from=build /app/publish .
ENTRYPOINT ["dotnet", "BlazorDockerDemo.dll"]
There are two stages in this Dockerfile.
The first stage uses the .NET SDK image. This contains everything required to restore dependencies, compile the application and publish it.
The second stage uses the much smaller ASP.NET Core runtime image. The published application is copied from the build stage into this image.
This approach means we do not need to ship the complete .NET SDK inside our production container.
Understanding the Build Stage
The first line selects the .NET 10 SDK image.
FROM mcr.microsoft.com/dotnet/sdk:10.0 AS build
The AS build part gives this stage a name. We can then refer to it later when copying the published application.
Next, we set the working directory:
WORKDIR /src
We then copy the project file into the container.
COPY ["BlazorDockerDemo.csproj", "./"]
Restoring the NuGet packages at this point has an additional benefit. Docker can cache this layer, meaning that if the project file has not changed, Docker does not necessarily have to download all the packages again every time the image is built.
RUN dotnet restore "BlazorDockerDemo.csproj"
We then copy the rest of the source code into the image.
COPY . .
Finally, the application is published in Release configuration.
RUN dotnet publish "BlazorDockerDemo.csproj" \
-c Release \
-o /app/publish \
--no-restore
The --no-restore option is used because the restore operation has already been performed.
The Runtime Stage
The second stage starts with the ASP.NET Core runtime image.
FROM mcr.microsoft.com/dotnet/aspnet:10.0 AS final
This image contains the ASP.NET Core runtime required to run our application without including the full SDK.
Microsoft provides official ASP.NET Core runtime images for supported .NET releases, and these images are intended for running ASP.NET Core applications in production.
We then create the application's working directory:
WORKDIR /app
The interesting part is the next instruction:
COPY --from=build /app/publish .
The --from=build tells Docker to copy the files from our earlier build stage rather than from the host computer.
This is one of the major advantages of a multi-stage Docker build. The final image only contains the published application and the runtime it needs.
The final instruction tells Docker what to execute when the container starts.
ENTRYPOINT ["dotnet", "BlazorDockerDemo.dll"]
At this point, the container has everything required to run the Blazor Server application.
Adding a .dockerignore File
Before building the image, it is also worth creating a .dockerignore file.
This prevents unnecessary files from being sent to Docker during the build.
For example:
bin/ obj/ .vs/ .git/ .gitignore Dockerfile README.md
There is little point copying locally compiled files into the Docker build because the application is going to be compiled inside the SDK container.
Keeping the build context small can also make Docker builds faster.
Building the Docker Image
Open a terminal in the directory containing the Dockerfile and run:
docker build -t blazor-docker-demo .
The -t option gives the image a name.
The final . tells Docker to use the current directory as the build context.
Docker will process the Dockerfile, restore the NuGet packages, compile the application and create the final image.
Once the build has completed, we can see the image using:
docker images
You should see an entry similar to:
REPOSITORY TAG IMAGE ID CREATED SIZE blazor-docker-demo latest abc123... moments ago ...
The exact image size will depend on the application and the base image being used.
Running the Container
With the image created, we can start a container:
docker run -d \
--name blazor-docker-demo \
-p 8080:8080 \
blazor-docker-demo
The -d option runs the container in the background.
The --name option gives the container a friendly name.
The -p 8080:8080 option maps port 8080 on the host machine to port 8080 inside the container.
Current ASP.NET Core container images use port 8080 as their default HTTP port, beginning with .NET 8.
We can now open a browser and navigate to:
http://localhost:8080
Our Blazor Server application should now be running from inside the Docker container.
Checking the Container
If something goes wrong, Docker provides several useful commands.
To see running containers:
docker ps
To see the application's output:
docker logs blazor-docker-demo
This is particularly useful with ASP.NET Core applications because startup errors, configuration problems and exceptions will normally appear in the container logs.
To open a shell inside the container, you can use:
docker exec -it blazor-docker-demo /bin/bash
Depending on the base image being used, a shell may not be available. In that situation, container logs are usually the better place to start troubleshooting.
Stopping the Application
When you want to stop the application, run:
docker stop blazor-docker-demo
The container can subsequently be started again with:
docker start blazor-docker-demo
If you want to remove the container completely:
docker rm blazor-docker-demo
The Docker image remains available, so another container can be created from it later.
Configuration and Environment Variables
One of the advantages of containerisation is that configuration can be supplied when the container is started rather than being baked into the image.
For example, an ASP.NET Core application can receive environment variables using Docker's -e option:
docker run -d \
--name blazor-docker-demo \
-p 8080:8080 \
-e ASPNETCORE_ENVIRONMENT=Production \
blazor-docker-demo
This allows the same image to be used in different environments.
For example, the development environment might use one database connection string while production uses another. The application image does not need to change; only its configuration does.
Sensitive information such as database passwords should not be hard-coded into a Dockerfile or committed to source control.
Blazor Server and Persistent Connections
There is an important difference between containerising a traditional web application and containerising a Blazor Server application.
Blazor Server maintains a real-time connection between the browser and the server. This connection is handled using ASP.NET Core's real-time communication infrastructure.
This means that a reverse proxy or load balancer sitting in front of the container needs to be configured appropriately.
If the application is eventually scaled across multiple containers, there are additional considerations around connection management and how clients are routed to application instances.
For a simple deployment with one container, however, there is normally nothing unusual that needs to be done.
HTTPS and Reverse Proxies
For a local Docker test, HTTP is perfectly adequate.
For a production deployment, you will normally want HTTPS.
One common approach is to place a reverse proxy such as Nginx, Apache or another proxy in front of the Docker container. The reverse proxy handles the public HTTPS connection and forwards requests to the ASP.NET Core container.
This also makes it easier to host several applications on the same server.
For example, a reverse proxy could receive:
https://example.com
and forward the request internally to:
http://blazor-docker-demo:8080
ASP.NET Core has specific guidance for running applications behind proxies and load balancers, particularly where forwarded headers and HTTPS are involved.
Docker Compose
Once the application starts depending on other services, Docker Compose becomes particularly useful.
For example, a typical Blazor Server application might eventually consist of the application itself and a SQL Server database.
A compose.yml file could define both services:
services:
blazor:
build: .
ports:
- "8080:8080"
environment:
ASPNETCORE_ENVIRONMENT: Production
database:
image: mcr.microsoft.com/mssql/server:2022-latest
environment:
ACCEPT_EULA: "Y"
MSSQL_SA_PASSWORD: "YourStrongPassword123!"
The exact database configuration would need to be adapted for the application, but the principle is straightforward.
Instead of manually creating several containers, Docker Compose can describe the entire application environment.
The environment can then be started with:
docker compose up -d
And stopped with:
docker compose down
This becomes particularly useful as an application grows.
Common Problems
One of the most common problems when containerising a .NET application is using the wrong version of the SDK or runtime image.
If the application targets .NET 10, use compatible .NET 10 container images. Likewise, an application targeting .NET 9 should use compatible .NET 9 images.
Another common problem is port mapping.
If the application is listening on port 8080 inside the container, this command:
-p 8080:8080
makes it available on port 8080 on the host.
Changing the first number changes the host port. For example:
-p 9000:8080
means the application is available at:
http://localhost:9000
while it continues listening on port 8080 inside the container.
It is also important to remember that localhost inside a container refers to the container itself. If your Blazor application needs to communicate with another container, you normally use the Docker Compose service name rather than localhost.
Why Dockerise a Blazor Server Application?
There are several advantages to taking this approach.
The development environment becomes more consistent because the application runs against a known .NET runtime.
Deployment becomes simpler because the server does not need the complete .NET SDK installed.
The application can be moved between machines without having to recreate the entire environment manually.
It also provides a natural foundation for more advanced deployment scenarios involving reverse proxies, databases, automated builds and cloud hosting.
Most importantly, Docker separates the application from the underlying server. The server needs Docker; Docker then provides the environment in which the application runs.
Conclusion
Dockerising a Blazor Server application is surprisingly straightforward once the basic concepts are understood. A multi-stage Dockerfile allows the application to be built using the .NET SDK and then placed into a smaller ASP.NET Core runtime image.
From there, Docker can take care of running the application as a container, while environment variables and external services can be introduced as the application becomes more complex.
For a small personal project, Docker may initially seem like extra work. However, once you start deploying applications to different machines or servers, having a repeatable and predictable deployment process can save a considerable amount of time.
The same approach also provides a useful foundation for taking a Blazor application from a development machine to a production environment without having to reinvent the deployment process each time.
Become a member
Get the latest news right in your inbox. It's free and you can unsubscribe at any time. We hate spam as much as we do, so we never spam!
Read next
Deploying a .NET App with Docker Compose
Docker makes it easy to package a .NET application and its dependencies into a consistent, portable environment. Docker Compose takes this a step further by allowing you to define and run your application and supporting services from a single configuration file. In this article, we’ll look at how to deploy a .NET application using Docker Compose.
Multi-Stage Docker Builds for .NET
