As a .NET developer, if you want to containerise your .NET apps, you know you need a Dockerfile
first. Dockerfile
is a spec that describes which OS is used, where the .NET code is located, how the .NET code is compiled and how the .NET app is executed. Once your Dockerfile is ready, you can build your container image. But is the Dockerfile the only option for building your .NET apps in a container? How about the container orchestration? Is there any way to orchestrate containers other than running the docker compose up
command? Throughout this post, I'm going to discuss which options are available for .NET developers to containerise your .NET apps.
You can find a sample code from:
devkimchi / msbuild-for-containers
This provides sample .NET apps using container images with Dockerfile, with MSBuild, and with .NET Aspire support on Docker Desktop.
MSBuild for Containers
As a .NET developer, when you build a container image for your app, you can use the
Dockerfile
to define the container image. However, you can also use thedotnet publish
command to build and publish the container image without aDockerfile
. This repository provides sample .NET apps using container images withDockerfile
and withdotnet publish
.In addition to that, if you want to orchestrate containers Docker Compose is usually the first approach. However, you can also use the .NET Aspire to generate the Docker Compose file from the .NET Aspire manifest JSON file. This repository also provides a sample .NET app using the .NET Aspire to orchestrate containers.
Prerequisites
- .NET SDK 8.0+ with .NET Aspire workload
- Visual Studio or Visual Studio Code + C# Dev Kit
- Aspirate
- Docker Desktop
Getting Started
Run with
Dockerfile
Run
docker init
to create a new Dockerfile for…
Prerequisites
There are a few prerequisites to containerise .NET apps effectively.
Containerise with Dockerfile
In the sample code repository, there are two .NET apps, ApiApp
and WebApp
. When you run both apps locally, it communicates with each other by running the following commands:
dotnet run --project ./MSBuildForContainers.ApiApp
dotnet run --project ./MSBuildForContainers.WebApp
Now, you want to containerise both apps. How can you do that? The first option is to write a Dockerfile
for each app. You can either manually write Dockerfile
or run the command, docker init
. Once you have Dockerfile
files for both apps, run the following commands to build the container images:
# For ApiApp
docker build . -t apiapp:latest
# For WebApp
docker build . -t webapp:latest
We all know how Dockerfile
is useful to build container images. However, Dockerfile
is yet another code and should be maintained. If you have many apps to containerise, you need to write Dockerfile
files respectively. This is a bit cumbersome. Is there any other way to containerise .NET apps, without writing Dockerfile
files?
Containerise with dotnet publish
MSBuild supports containerisation by itself. It uses the dotnet publish
command to build the container image for each app. To do this, you might need to update your .csproj
file to include the containerisation settings. The following is the MSBuildForContainers.ApiApp.csproj
file:
<PropertyGroup>
<ContainerRepository>apiapp</ContainerRepository>
<ContainerImageTag>latest</ContainerImageTag>
</PropertyGroup>
These two properties replaces the --tag
option in the docker build
command. Once you have these properties in the .csproj
file, run the following command. Note that the -t:PublishContainer
indicates the containerisation and the --os
and --arch
options specify the target OS and architecture.
dotnet publish ./MSBuildForContainers.ApiApp \
-t:PublishContainer \
--os linux --arch x64
Then, you'll have the container image. If you want to change the base image to the chiseled container one, add another property to the .csproj
file. The following property sets the chiseled Ubuntu 24.04 image as the base image.
<PropertyGroup>
<ContainerBaseImage>mcr.microsoft.com/dotnet/aspnet:8.0-noble-chiseled</ContainerBaseImage>
</PropertyGroup>
Then, run the dotnet publish
command above again. This time, the container image is built based on the chiseled image. Let's do the same thing against the MSBuildForContainers.WebApp.csproj
file. This way, you can containerise your .NET apps without writing Dockerfile
files.
Alternatively, you don't even need those properties in the .csproj
file. You can run the following command to build the container image directly like this:
dotnet publish ./MSBuildForContainers.ApiApp \
-t:PublishContainer \
--os linux --arch x64 \
-p:ContainerBaseImage=mcr.microsoft.com/dotnet/aspnet:8.0-noble-chiseled \
-p:ContainerRepository=apiapp \
-p:ContainerImageTag=latest
You have the same development experience as building the container image with the docker build
command, without having to rely on Dockerfile
.
Now, you've got chiseled container images for both API app and web app. How to let them talk to each other?
Container orchestration with docker compose
The existing web app container image can't talk to the API app container image because it doesn't know where the API app container is. To make the web app container talk to the API app container, you need to slightly modify the web app and build the container image again. Open the MSBuildForContainers.WebApp/Program.cs
file and update the base address value as follows:
// Before
builder.Services.AddHttpClient<IApiAppClient, ApiAppClient>(http => http.BaseAddress = new Uri("https://localhost:5051/"));
// After
builder.Services.AddHttpClient<IApiAppClient, ApiAppClient>(http => http.BaseAddress = new Uri("http://apiapp:8080/"));
Run the dotnet publish
command again to build the web app container image. Now, you can manually run the docker run
command to run both containers by attaching the same network. But for the container orchestration, the docker compose up
command is much easier to use. The docker-compose.yml
file is already prepared in the sample code repository. Run the following command:
docker compose -f ./docker-compose.yaml up
Then, both containers are up and running. The web app container can talk to the API app container. This is how you can orchestrate containers with docker compose up
. Again, this Docker Compose file is yet another code and should be maintained. If you have many apps to orchestrate, how would you manage them?
Container orchestration with .NET Aspire and Aspirate
.NET Aspire is to orchestrate .NET apps in containers. For this Docker Compose orchestration purpose, Aspirate is used, which is a tool that generates the Docker Compose file, a Kustomize file or helm file for the container orchestration. To use Aspirate, you need to install it first:
dotnet tool install -g aspirate
In the sample code repository, Both web app and API app are orchestrated with .NET Aspire in another branch. Switch to the aspire
branch with the command:
git switch aspire
Let's generate the Docker Compose file with Aspirate. Run the following command to generate the .NET Aspire manifest file first:
dotnet run --project ./MSBuildForContainers.AppHost \
-- \
--publisher manifest \
--output-path ../aspire-manifest.json
Then, generate the Docker Compose file with the Aspirate command. Note that it intentionally excludes the .NET Aspire dashboard.
aspirate generate \
--project-path ./MSBuildForContainers.AppHost \
--aspire-manifest ./aspire-manifest.json \
--output-format compose \
--disable-secrets --include-dashboard false
Now, you have the Docker Compose file generated. Run the docker compose up
command to orchestrate the containers. This way, you can orchestrate your .NET apps in containers without manually writing the Docker Compose file.
So far, I've walked through how .NET developers can containerise their .NET apps with different options. You can either write Dockerfile
files or use dotnet publish
to build container images. You can orchestrate containers by writing the Docker Compose file by hand or letting .NET Aspire and Aspirate generate it automatically. Which one do you prefer?
More about MSBuild for Containers?
If you want to learn more options about containers with MSBuild, the following links might be helpful.
Top comments (0)