Scott Hanselman

Making a tiny .NET Core 3.0 entirely self-contained single executable

June 14, 2019 Comment on this post [18] Posted in DotNetCore
Sponsored By

I've always been fascinated by making apps as small as possible, especially in the .NET space. No need to ship any files - or methods - that you don't need, right? I've blogged about optimizations you can make in your Dockerfiles to make your .NET containerized apps small, as well as using the ILLInk.Tasks linker from Mono to "tree trim" your apps to be as small as they can be.

Work is on going, but with .NET Core 3.0 preview 6, ILLink.Tasks is no longer supported and instead the Tree Trimming feature is built into .NET Core directly.

Here is a .NET Core 3.0 Hello World app.

225 files, 69 megs

Now I'll open the csproj and add PublishTrimmed = true.

<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>netcoreapp3.0</TargetFramework>
<PublishTrimmed>true</PublishTrimmed>
</PropertyGroup>
</Project>

And I will compile and publish it for Win-x64, my chosen target.

dotnet publish -r win-x64 -c release

Now it's just 64 files and 28 megs!

64 files, 28 megs

If your app uses reflection you can let the Tree Trimmer know by telling the project system about your Assembly, or even specific Types or Methods you don't want trimmed away.

<ItemGroup>
<TrimmerRootAssembly Include="System.IO.FileSystem" />
</ItemGroup>

The intent in the future is to have .NET be able to create a single small executable that includes everything you need. In my case I'd get "supersmallapp.exe" with no dependencies. That's done using PublishSingleFile along with the RuntimeIdentifier in the csproj like this:

<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>netcoreapp3.0</TargetFramework>
<PublishTrimmed>true</PublishTrimmed>
<PublishReadyToRun>true</PublishReadyToRun>
<PublishSingleFile>true</PublishSingleFile>
<RuntimeIdentifier>win-x64</RuntimeIdentifier>
</PropertyGroup>
</Project>

At this point you've got everything expressed in the project file and a simple "dotnet publish -c Release" makes you a single exe!

There's also a cool global utility called Warp that makes things even smaller. This utility, combined with the .NET Core 3.0 SDK's now-built-in Tree Trimmer creates a 13 meg single executable that includes everything it needs to run.

C:\Users\scott\Desktop\SuperSmallApp>dotnet warp
Running Publish...
Running Pack...
Saved binary to "SuperSmallApp.exe"

And the result is just a 13 meg single EXE ready to go on Windows.

A tiny 13 meg .NET Core 3 application

If you want, you can combine this "PublishedTrimmed" object with "PublishReadyToRun" as well and get a small AND fast app.

<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>netcoreapp3.0</TargetFramework>
<PublishTrimmed>true</PublishTrimmed>
<PublishReadyToRun>true</PublishReadyToRun>
</PropertyGroup>
</Project>

These are not just IL (Intermediate Language) assemblies that are JITted (Just in time compiled) on the target machine. These are more "pre-chewed" AOT (Ahead of Time) compiled assemblies with as much native code as possible to speed up your app's startup time. From the blog post:

In terms of compatibility, ReadyToRun images are similar to IL assemblies, with some key differences.

  • IL assemblies contain just IL code. They can run on any runtime that supports the given target framework for that assembly. For example a netstandard2.0 assembly can run on .NET Framework 4.6+ and .NET Core 2.0+, on any supported operating system (Windows, macOS, Linux) and architecture (Intel, ARM, 32-bit, 64-bit).
  • R2R assemblies contain IL and native code. They are compiled for a specific minimum .NET Core runtime version and runtime environment (RID). For example, a netstandard2.0 assembly might be R2R compiled for .NET Core 3.0 and Linux x64. It will only be usable in that or a compatible configuration (like .NET Core 3.1 or .NET Core 5.0, on Linux x64), because it contains native code that is only usable in that runtime environment.

I'll keep exploring .NET Core 3.0, and you can install the SDK here in minutes. It won't mess up any of your existing stuff.


Sponsor: Suffering from a lack of clarity around software bugs? Give your customers the experience they deserve and expect with error monitoring from Raygun.com. Installs in minutes, try it today!

About Scott

Scott Hanselman is a former professor, former Chief Architect in finance, now speaker, consultant, father, diabetic, and Microsoft employee. He is a failed stand-up comic, a cornrower, and a book author.

facebook twitter subscribe
About   Newsletter
Hosting By
Hosted in an Azure App Service
June 17, 2019 11:13
Lovely, gone are the times when a single Windows Forms app with one Button that would display a MessageBox with "Hello World" would use a 100 megabytes of memory... :-P
June 17, 2019 11:14
.NET core self-contained assemblies is pure magic!
June 17, 2019 12:47
How is this related to the --self-contained paramerer of the dotnet publish command?
June 17, 2019 19:23
Great post - minimizing the output is a wet dream for most developers - I hope :)


I don't think there is any mention of a parameter called --self-contained in the this post; or in the comment section, for that matter.
However :) The term self-contained is used to express what the goal is.
June 17, 2019 20:09
Long way to go to get down to a 402 bytes hello world exe, but a great step in the right direction. It would be fun to see a multi-platform R2R .exe :)
June 18, 2019 0:15
Sadly, in the preview6, asp.netcore or blazor app fails to be single exe compiled.
June 18, 2019 3:31
I love that 13 MB is a small app now :)

I remember when Netscape Navigator hit 6MB on my dial up 14.4k modem and though it was the most evil thing would leave on overnight to download.
June 18, 2019 13:39
@Justin King : my thoughts exactly. I seem to remember that the whole Windows 3.1 fit on a bunch of diskettes, that you could hold in one hand. 5 or 6 or so. I agree that the reduction in size within this article is a lot, but 13 megabytes for "hello world", still makes me gringe worse that I would like to :-D
June 18, 2019 16:11
Truth be told, a lot of these newfangled compiled languages produce similar sized executables when compiling an "Hello, World!"
June 18, 2019 20:49
This looks great, but is doesn't seem to copy any assets across (images for example).
June 18, 2019 22:00
Just to remember, compressed executable files make virtual memory manager to work a little harder - decompressed data has to be written to disk to free up the memory instead of simply discarding unused data blocks and reloading them from the executable image if needed again.
June 19, 2019 21:18
@Daniel - I just created a WinForms app with three buttons and a rich text box. 4.3MB in memory on x86. Did you mean 10MB and not 100MB?
June 20, 2019 15:29
Hi Scott,
Nice blog post :-)

Is there a way(or will there be in the future) to create a "self-contained single executable" for a asp.net core or Blazor (ASP.NET Core hosted) app ?
June 21, 2019 19:44
13 meg.. sounds like an achievement. What are you trying to push here?

Still no answers about the HUGE security problems you have when distribute .net core apps.
June 21, 2019 20:33
This doesn't work on WPF. I've added

PublishReadyToRun>true</PublishReadyToRun>


and published with

dotnet publish -r win-x64 -c release


but the size is pretty 100% identical. Moreover, there is no performance improvements at all. In your example it shows

<OutputType>Exe</OutputType>


but shouldn't it be

<OutputType>WinExe</OutputType>


as it is for WPF? What kind of application did you use? Winforms? WPF? Console only? I also couldn't find any documentation about this, whether it supports WPF or just winforms or just console only or whether it has been tested with WPF at all, seems like not.

Using .NET Core 3 Preview 7.
Bob
June 24, 2019 16:07
"Still no answers about the HUGE security problems you have when distribute .net core apps."

Edward, can you elaborate on the security problems you're referring to?
June 25, 2019 12:43
I would be more concerned about all of the libraries being in the same directory as the executable, whether you have 75 or 17 DLLs in the same directory as the executable the user has to click on, they will find it intimidating. Any word on being able to simply put the DLLs in a separate folder?
June 25, 2019 20:59
I'm just very curious to know if there are any plans to allow these types of builds to be easily distributable without the hosting bundle for IIS integration?

Currently, the ASP.NET Core Hosting Module is only offered via the hosting bundle that includes both x32 and x64 versions of the respective run time, which from a software distribution standpoint kills the benefit of the self-contained and/or trimmed builds.

Don't get me wrong, this is all well and good for command line/desktop applications but still leaves the ASP.NET Core side of the fence out to dry because of how MS offers .NET Core downloads.

Comments are closed.

Disclaimer: The opinions expressed herein are my own personal opinions and do not represent my employer's view in any way.