all Technical posts

Containerizing a .NET App Without Using Dockerfiles

Are you tired of writing Dockerfiles for your .NET applications? Do you want to containerize your .NET applications without using Dockerfiles? Then read on!

As a modern company focused on the cloud, you might think we all love Docker and Containers. But the truth is some (amazing) application developers believe this is unnecessary, as they are used to Zipping their files and deploying them to a PaaS service. They are not containerization people: they are .NET developers. And it seems that Microsoft agrees with them. Microsoft believe that writing Dockerfiles is unnecessary and that publishing a .NET application as a container should be as easy as any other deployment.

In this blog post, I will take a deep dive into containerizing a .NET application without the use of Dockerfiles, and show you how to use the dotnet publish command to publish a .NET application as a container.

The Start: Build Image Tool

This is an open-source tool that was developed in April 2022. It calls itself “A .NET global tool to create container images from .NET projects, because life is too short to write Dockerfiles.”

I will not focus on this, but feel obliged to give a quick demo, as it is quite cool! You can find the tool here. Installing it is as easy as running the following command:

After installing the tool, you can run the following command to build an image:

This generates a temporary Dockerfiles and then builds the image. For my API, the Dockerfiles looks like this:

Current Tool: dotnet Publish

This is a new feature added to the .NET 8.0 SDK (.NET 7.0 as a NuGet package). It allows your .NET publish to output a container image. The official documentation with all the options can be found here.

dotnet worker

As with the demo, I will showcase a worker application along with other types of .NET applications. As you’ll see, creating a container image for any resource is straightforward. Simply add the following line to your csproj file:

The container needs a name. After this, it is as simple as running the following command:

A demo can be found here and in the official documentation here.

Console application

Creating a container image for a console app is very similar to the above, with one small difference. You need to add the following lines to the csproj file:

Then run the following command:

Demo

This quick demo (code can be found here) uses the battleship game from this open source project. If you look at the csproj file you will see the following lines:

In the terminal you can see the following output:

If I then run it in docker desktop, I see a working battleship game.

API

Creating a container image for an .NET API is extremely easy. You just need to add the following lines to the csproj file:

After this, it is as simple as running the following command:

Azure Function

Creating a container image for an Azure Function is also very easy. You just need to add the following lines to the csproj file:

Then you run:

You will discover then that the Azure Function is not working. This feature is unfortunately not yet supported by the Azure Functions runtime. You can only publish a function as a container if you have a Dockerfile. You can follow the official issue here and here for updates. I really hope they add this soon, as Azure Functions are what I mainly program.

Other Languages

This feature is a .NET feature but the demos only use C# code. I was wondering if this also works on the other languages in the .NET ecosystem and so decided to test F# and VB.NET.

F#

I developed an F# API, which is a basic API, and to my surprise, it functions exactly the same way as the C# API. I only needed to add the following lines to the fsproj file:

Then run the following command:

And you’ll get a working F# API in a container.

VB.NET

I also wrote a VB.NET console app. To be clear: we never use VB.NET at Codit, but I wanted to test it. To my surprise, it works the same way as the C# console app. The code and demo can be found here.

DevOps

This is all great, but I won’t type the command into the terminal when I want to use CI/CD. However, there are no clear guides online, so I will make one for you.

I’m assuming that you have modified the csproj file as shown in the previous sections. The first task is always a docker login into your ACR:

Basic API release

For this demo, I will use the very basic weather forecast API that is created when you create a new .NET API. I will use the following task:

If you set publishWebProjects to “true”, it will publish all APIs in the solution. As you can see, there is nothing complicated about this. In the ACR you can see the following image:

220.46 MB is a nice size, but what if I want it to be alpine based but only support linux-x64? This you can find in the next section.

Advanced API release

The publish feature allows you to add a lot of arguments. In the following tasks, I included some that are useful in my opinion:

This will create a smaller image, with a size of 115.81 MB. Let’s go over the arguments:

  • ContainerRepository: The name of the container (this overwrites the name in the csproj file).
  • ContainerImageTags: This is a very tricky argument. You have both. ContainerImageTag and ContainerImageTags: If you pick the tags that the official documentation tells you to, you need to divide them with a semicolon. But they also tell you that in CI/CD cases you might need to do it like this: “1.0.0;latest”. This does not work on Azure DevOps, and after quite a bit of testing I found out that “\”1.0.0;latest\”” does work.
  • Version: The version of the API.
  • ContainerRegistry: The ACR.
  • ContainerUser: The user that runs the container. I set it to root, but it is recommended to set it to a non-root user.
  • ContainerFamily: The type of base image: I set this to alpine. You can also set the base image itself, but I recommend the family, because this will update with the .NET version of the project automatically.
  • ContainerRuntimeIdentifier: The runtime identifier. I set this to linux-x64.

Private NuGet feed

After a talk with our Containerization Lead, I discovered that one of the things they need to deal with is private NuGet feeds. It is much easier to do that this way than with Dockerfiles. As we use the .NET build and then later package it into a Docker, we don’t need to deal with anything special: it is the same as any other .NET project. You just need to add the nuget.config, do a dotnet restore and then a dotnet publish. The Yaml pipeline looks like this:

 

Drawbacks

While this is a very easy way to containerize a .NET application, there are some drawbacks. The biggest is that you no longer make the Dockerfile, which reduces your knowledge of the underlying technology. You are also limited in your options. And while there are a lot of arguments you can add to the publish command, after a while you might ask whether it’s easier just to write a Dockerfile. It also abstracts the containerization process: in the Azure Function example, you can see that I set a ContainerBaseImage. But is this used as the run image or both the build and run image… or in another way altogether?

Future: .NET Aspire?

There might be another big shift coming in .NET containerization with .NET Aspire. I’m not yet an expert on this, but this method overrides the need for things like docker images, and container registries. You just make a .NET Aspire application and then you can generate a manifest JSON file based on the application. This manifest will describe the full application and all its dependencies, and then with the Azure developer CLI (azd) you can just run azd init & azd up.

Conclusion

This useful feature removes the need to update the Dockerfiles every time you execute a .NET bump, and is a very practised way of doing things for our .NET developers. However, I hope that they add the Azure Function support soon.

Thanks for reading! I hope this blog post was useful to you. If you have any questions or remarks, feel free to contact me.

Subscribe to our RSS feed

Hi there,
how can we help?

Got a project in mind?

Connect with us

Let's talk

Let's talk

Thanks, we'll be in touch soon!

Call us

Thanks, we've sent the link to your inbox

Invalid email address

Submit

Your download should start shortly!

Stay in Touch - Subscribe to Our Newsletter

Keep up to date with industry trends, events and the latest customer stories

Invalid email address

Submit

Great you’re on the list!