End of summer 2022, the .NET team at Microsoft announced two things related to containers: .NET in Chiseled Ubuntu containers and then a week after built-in container support in the .NET 7 SDK. I have talked about both topics on two episodes of the French podcast devdevdev.net by my friend Richard Clark. In this post, I will explain what those are and how to combine them.
.NET in Chiseled Ubuntu containers
.NET in Chiseled Ubuntu containers are new small and secure containers offered by Canonical. Those images are 100MB smaller than the Ubuntu image.
Why does it improve security? Because small images reduce the attack surface. Based on images with only the packages required to run the container, no package manager, no shell, and non-root user.
The idea originates from distroless concept introduced in 2017 by Google. On top of that, it brings the following features: polished tools and trusted packages from platform providers, choice of free or paid support, choice of fast-moving releases or LTS, possibility to customize the images.
To take advantage of those new images, in .NET 6 and 7 for Arm64 and x64, you need to use one of the three layers of Chiseled Ubuntu container images.
- mcr.microsoft.com/dotnet/nightly/runtime-deps:7.0-jammy-chiseled
- mcr.microsoft.com/dotnet/nightly/runtime:7.0-jammy-chiseled
- mcr.microsoft.com/dotnet/nightly/aspnet:7.0-jammy-chiseled
Today this is still in preview, and images are served from the nightly repository. Final images will be available before end of this year.
Built-in container support in the .NET 7 SDK
You can use Dockerfile to bundle your .NET application in a container. You create a Dockerfile, install the .NET SDK, restore the packages, build the application, and copy the output in the container. It implies quite some steps and is not easy to do right.
Today, .NET 7 SDK supports container images as a new output type through a simple dotnet publish. This was already the case on other platforms Ko for Go, Jib for Java, and even in .NET with projects like konet.
It requires only adding one NuGet package reference to the project file:
1 | <PackageReference Include="Microsoft.NET.Build.Containers" Version="0.2.7"/> |
In the future it will be part of the SDK, so you will not need to add this NuGet package reference.
Currently, only Linux containers are supported.
Then you can run the following command:
1 | ❯ dotnet publish --os linux --arch x64 -p:PublishProfile=DefaultContainer -c Release |
The image created is 216MB
1 | ❯ docker image list |
You can control many of the properties of the image created, like the image name and tags, through MSBuild properties. See, Configure container image.
1 | <PropertyGroup> |
After dotnet publish, the image with the new name and version is created
1 | ❯ docker image list |
Can we combine the two?
Sure. We can combine the two and create a .NET application in a Chiseled Ubuntu container using the .NET SDK. It is a great way to create a small and secure container for your .NET application.
We need to add MSBuild property ContainerBaseImage to our csproj file to specify the Chiseled Ubuntu base image to use, in this case, aspnet:7.0-jammy-chiseled.
1 | <PropertyGroup> |
This time the image is 112MB compared to the 216MB. We won 104MB on top of all other advantages from the Chiseled Ubuntu container.
1 | ❯ dotnet publish --os linux --arch x64 -p:PublishProfile=DefaultContainer -c Release |
Can we do better?
Yes, we can 😉 mixing chiseling and trimming.
We can use the base image runtime-deps:7.0-jammy-chiseled, which is 13MB and publish our ASP.NET application as a self-contained and trimmed application.
1 | <PropertyGroup> |
We need to add –self-contained to the dotnet publish command. Now, the image is 56.5MB compared to the 112MB, we won 55.5MB.
1 | ❯ dotnet publish --os linux --arch x64 -p:PublishProfile=DefaultContainer -c Release --self-contained |
Can we do even better?
Yes, we can by publishing to a single file
1 | <PropertyGroup> |
We need to add -p:PublishSingleFile=true to the dotnet publish command. Now, the image is 48.4MB compared to the 56.5MB, we won 8.1MB.
1 | ❯ dotnet publish --os linux --arch x64 -p:PublishProfile=DefaultContainer -c Release --self-contained true -p:PublishSingleFile=true |
A tiny bit better?
If you don’t need globalization in your application, you can turn on the globalization invariant mode.
1 | <PropertyGroup> |
Finally, the image is 48.3MB compared to the 48.4MB. We won 0.1MB.
1 | ❯ dotnet publish --os linux --arch x64 -p:PublishProfile=DefaultContainer -c Release --self-contained true -p:PublishSingleFile=true |
I expected better results with this last step. The runtime-deps:6.0-jammy-chiseled removes ICU and sets DOTNET_SYSTEM_GLOBALIZATION_INVARIANT=true
. I guess runtime-deps:7.0-jammy-chiseled does the same, but the code is not yet available.
Hey @runfaster2000 and @ValentinViennot Great presentation during the #dotnetConf 👍🏼 Any plan to ship another 7.0-jammy-chiseled without ICU?
— Laurent Kempé (@laurentkempe) November 13, 2022
Run
1 | ❯ docker run --rm -it -p 8080:80 dotnet-webapp-chiseled-trimmed-singlefile-invariant:1.1.0 |
Then you can browse to http://localhost:8080/weatherforecast to see
Conclusion
Now that .NET 7 RTM shipped, we can leverage those new capabilities. It lets us create small and secure containers for our .NET applications easily.
We went from a first docker image of 216MB to a final docker image of 48.3MB. That’s more than a 77% reduction in size.
I hope you enjoyed this post and learned something new that you want to try out. If you have any questions or comments, please leave them below. Thanks for reading!
Code
Presentation
Using .NET with Chiseled Ubuntu Containers
Chiseled Ubuntu Containers are new and exciting. You’ll see how easy it is to switch to using them with .NET and what the benefits are. We’ll show using them at your desktop, deploying them to the cloud, and also making an evil hacker fail to compromise an app (that might otherwise succeed).
.NET in Ubuntu and Chiseled Containers - Canonical & Microsoft
Folks at Microsoft reached out to Canonical with a “simple” request, for Ubuntu distroless container images. The two companies partnered together to produce super small and secure container images, about 100MB smaller than regular container images. You can start using these images now, for much better performance.