test

.NET Core 3.0 Single-File Apps

Build & Publish Single-File Self-Contained Applications, Then Make Them Smaller!

.NET Core 3.0 Single-File Apps

Build & Publish Single-File Self-Contained Applications, Then Make Them Smaller!

Self-contained deployments aren’t new to .NET Core, but when you build a self-contained application you typically end up with a massive deployment folder filled with every possible DLL you’d need, and then some. Keep reading and I’ll tell you how to avoid that with .NET Core 3.0.

Single-File Executables

Imagine that the dotnet publish command created just one file rather than creating directory with a few hundred files. With .NET Core 3.0 the dotnet publish command allows you to package an app into a platform-specific single-file executable. You can generate this executable two ways, both methods require a .NET Core 3.0 project.

Update your project file

The first way to generate the single-file executable is to set the PublishSingleFile property in your .csproj file the run a dotnet publish:

<PropertyGroup>
  <RuntimeIdentifier>linux-arm</RuntimeIdentifier>
  <PublishSingleFile>true</PublishSingleFile>
</PropertyGroup>

This is a convenient “set and forget” way to generate your single-file executable.

Explicit run via console

If you prefer to not update the project file, you can also run dotnet publish with the PublishSingleFile parameter:

dotnet publish -r linux-arm /p:PublishSingleFile=true

After publishing your single-file assembly you may notice that the file can be rather large. In my tests a simple “Hello World!” console app generates an executable around 66 MB. Not terrible, but we can make it smaller.

Assembly Linking

Also new in the .NET Core 3.0 SDK is a tool that analyzes the IL and removes unused assemblies, thereby making the executable smaller. Self-contained applications include everything needed to run the code, but often it isn’t all needed. .NET Core 3.0 now allows you to make use of an IL linker tool to determine what libraries are actually required, and remove those that aren’t.

To use Assembly Linking in your .NET Core 3.0 project, add the <PublishTrimmed> property in your project and publish your app as a self-contained app:

Update your project file

<PropertyGroup>
  <PublishTrimmed>true</PublishTrimmed>
</PropertyGroup>

Publish your application

dotnet publish -r linux-arm -c Release

Using <PublishTrimmed> can significantly decrease the size of your self-contained app. The “Hello World!” app I mentioned earlier went from around 66 MB to just over 25 MB, that’s a big difference.

One thing I’ll stress is that if you’re going to use <PublishTrimmed> for your code, make sure you test it well. If you make use of reflection or other dynamic features the IL linker tool may not know that an assembly is used and remove it. The good news is that you can configure the tool to be aware of libraries that must always be included, find out more in the docs.

ReadyToRun Images

A final feature new to .NET Core 3.0 that fits nicely with Single-File Executables and Assembly Linking is ReadyToRun images. ReadyToRun applications have an improved startup time by reducing the amount of work the JIT compiler needs to do when the application starts up. The published app contains a bit more code to help accomplish this so the applications will be slightly larger - my “Hello World!” sample app size went up less than half a MB.

The ReadyToRun feature is only available if you’re publishing a self-contained application targeting a specific runtime environment (e.g.: linux-arm or win-x64). To publish your project as ReadyToRun you’ll need to update your project file and then publish your application.

Update your project file

Add the <PublishReadyToRun> setting to your project

<PropertyGroup>
  <PublishReadyToRun>true</PublishReadyToRun>
</PropertyGroup>

Publish your application

dotnet publish -r linux-arm -c Release

Conclusion

A lot of my recent focus on .NET Core has been with the Raspberry Pi. The new features I’ve written about in this article are very interesting to me for deploying functionality to my Raspberry Pi. Deploying & managing a single file has been very handy and I’m really looking forward to moving to the final version of .NET Core 3.0 when it is released.

Note: In the above examples above you don’t have to publish to the linux-arm runtime, it is just the runtime I’ve been using. Find other Runtime Ids in the docs.

If you’d like to read about .NET Core on the Raspberry Pi, checkout out my post on .NET Core 2.2 on the Raspberry Pi!