November 2018

Volume 33 Number 11

.NET Core - Publishing Options with .NET Core

By Jamie Phillips | November 2018

The advent of .NET Core brings a new paradigm for publishing your applications with the introduction of two new techniques: framework-dependent deployments (FDDs) and self-contained deployments (SCDs). Each option has advantages and disadvantages that need to be considered before publishing. In this article, I explore both approaches by building a sample application and discussing the strengths and weaknesses of each. I then take a brief look at an additional deployment option—CoreRT—that’s in the early stages of development.

Publishing with .NET Core brings a lot of information to consider. Traditionally, the typical deployment option for a desktop or console application in the .NET Framework has been an executable. For an ASP.NET application, it’s a DLL. These were the only available options and they were dependent on the type of application being built. With the new paradigm, the significant aspect is whether .NET Core is installed or not. The FDD technique requires that .NET Core be installed while the SCD technique brings the .NET Core runtime with it, which means it can run on a machine that doesn’t have .NET Core installed. An ASP.NET application can now additionally be packaged as a standalone console application because it brings its own Web server (Kestrel) as an executable.

For my exploration of the two approaches, I’ll use .NET Core SDK 2.1 along with a command-line interface. You can find instructions for installing .NET Core 2.1 at microsoft.com/net/download. I’m using PowerShell almost exclusively to run all the commands that I show in the article, but the command prompt (cmd.exe) should work just fine, as well, and, in fact, these commands will execute on the shells of other OSes, including macOS and Linux. Finally, to experiment with SCDs, the Windows Subsystem for Linux (WSL) is invaluable for enabling first-hand testing of any executables created for Linux. You can learn more about WSL at bit.ly/2Nj7FuQ.

Sample Application

I’m going to create a very basic sample application for testing the two publishing methods using .NET Core 2.1. This will be a console application that prints hello along with the name provided. You can use either cmd.exe or PowerShell to input the following code:

> dotnet new console -o Publishing

Once the new console application has been created, I open Program.cs and enter the following code:

using System;
namespace Publishing
{
  class Program
  {
    static void Main(string[] args) => Console.WriteLine($"Hello {args[0]}!");
  }
}

When I execute the application, I see this:

> dotnet run -- Jamie

Hello Jamie!

As you can see, it works as expected. As a quick aside, the two hyphens syntax (--) is how parameters are passed to the application when using the “dotnet run” command. With my console application complete, I can dive into the publishing methods.

The Publish Command

First, let’s look at the options available with the publish command by executing it with the “-h” (help) option:

dotnet publish [<PROJECT>] [-c|--configuration] [-f|--framework] [--force]
                           [--manifest]
                           [--no-build] [--no-dependencies] [--no-restore]
                           [-o|--output]
                           [-r|--runtime] [--self-contained]
                           [-v|--verbosity] [--version-suffix]

Note that two of these options are especially useful when publishing .NET Core applications: --runtime and --self-contained. These options are used together; runtime specifies which runtime or runtimes should be targeted when creating an SCD, while self-­contained signals that a self-contained package with the runtime needs to be created. If the runtime option is passed, the self-­contained option is automatically applied.

Other choices to note are the no-restore and no-build options. The no-restore option won’t execute the implicit restore when running the publish command. The no-build option won’t build the project or run the restore command, so the publish command will run against the existing build. This is useful in continuous integration/continuous deployment scenarios because a build and restore won’t be triggered multiple times. Last, the framework option allows the framework version to be specified. The official documentation can be found at bit.ly/2pizcOL.

Framework-Dependent Deployments

FDDs are the default when executing the publish command under the .NET Core CLI. The publish command creates a platform-­agnostic application that can run on any .NET Core runtime that’s equivalent to or newer than the minor version used to build the application (though version 2.0 won’t run something that targets 1.x, and 3.0 won’t run something that targets 2.x). Because the targeted runtime isn’t packaged with the application, this option yields the smallest package. If several applications are going to be deployed to a system, allowing the runtime to be shared among the applications reduces overall memory and disk usage on the system. Another advantage of the shared runtime is that any future runtime updates will apply to all applications.

Let’s walk through publishing the sample application as an FDD by entering the following at the command line:

> dotnet publish

This creates output in the bin\Debug\netcoreapp2.0 folder, including the file publishing.dll. This 5KB DLL is the application. Next, run the application using the dotnet CLI, like so:

> dotnet Publishing.dll Jamie

Hello Jamie!

That’s all there is to it.

FDD provides several benefits, including a smaller deployment package, a system-managed runtime, and the ability to share the runtime among multiple apps to reduce disk and memory usage. The app is also guaranteed to run on any platform that has a compatible runtime installed.

The disadvantages of FDDs are that system-wide runtime updates could cause issues as they might be incompatible, and that your application can only run on the framework version (or newer) with which it was compiled. It also requires users to have installed that version of the .NET Core runtime on their machine prior to executing the application.

Self-Contained Deployment

Now let’s look at the second approach to publishing provided with the .NET Core CLI—SCDs. I’ll use the same publishing command as I did before, but this time I’ll pass a runtime identifier (RID) as an option to the publish command. The RID tells the .NET Core CLI which platform or platforms to target when creating the application. In this example, I’ll create a Windows 10 x64 build of the application. Any platform can be targeted from any OS, allowing executables for other OSes to be created from Windows. Here’s the command I use for this sample:

> dotnet publish -r win10-x64

Notice that there’s a subfolder created under the netcoreapp2.0 folder that’s named after the target runtime passed. Inside the win10-x64 folder, you’ll see that there’s now an executable instead of a DLL. That executable is 77KB, which is 72KB more than the previous application. I can run the executable to show that it still works:

> .\Publishing.exe Jamie

Hello Jamie!

Now that I have an SCD for Windows 10 x64, I’ll build one that targets Ubuntu x64, like so:

> dotnet publish -r ubuntu-x64

I can then utilize WSL to test that the Ubuntu version executes as desired. I open WSL from Windows and make the newly created application executable, so I can I execute it to make sure it’s working. Here’s that input and the resulting output:

> chmod +x Publishing
> ./Publishing Jamie

Hello Jamie!

The upside of SCD is that the .NET Core runtime is controlled and shipped with your application. This is convenient for users, as they aren’t required to install the runtime, and any system runtime changes won’t impact your application. On the other hand, SCD creates larger deployment packages because the runtime is shipped with the application. You also need to know your target platforms in advance to generate packages for each target. Moreover, every time there’s a runtime update that includes security or bug fixes, you must republish every application on the machine to get these fixes rather than installing a single machine-wide runtime update.

In the past, the approach to distributing .NET applications had been to require the .NET Framework to be installed or to package it with the installer. With .NET Core and SCDs, there’s no longer a need to create a special installer to deliver a basic application.

As positive as SCD is, there’s still one looming issue when deploying these applications to a Linux system. The runtime depen­dencies for .NET Core aren’t included with SCDs, which means users won’t need to install the runtime. However, users will need to install six dependencies from the package manager for the Linux distribution. For example, on Ubuntu the following dependencies will still need to be installed:

liblttng-ust0
libcurl3
libssl1.0.0
libkrb5-3
zlib1g
libicu52 (for 14.x)
libicu55 (for 16.x)
libicu57 (for 17.x)
libicu60 (for 18.x)

To resolve this issue, a developer needs to package the SCD application in a native package format for the distribution so additional dependencies can be defined.

While this may be a negative, the positive is that users don’t have to add any of the additional package repositories that would be required if the .NET Core runtime needs to be installed.

SCD is fascinating and deserves more discussion. Though there are several pieces that go into making SCD work, as part of the .NET CLI installation there are two components that really contribute to this. The first component is the shared runtime, which is a redistributable version of the .NET Core runtime and is consumed by the CLI and end users. The second component is the shared host, which is responsible for consuming the DLL that’s generated as part the publish process. The shared host is a generic apphost that allows any .NET Core library (DLL), to be executed as an application. When “dotnet run my.dll” is executed, my.dll is being hosted inside of this shared host. When the SCD application is packaged, what’s happening is that the shared runtime, the shared host, and the application DLL are placed together in an executable package, an .exe for Windows, or an appropriate executable file for Linux and macOS. The actual documentation for this design can be found in the .NET CLI repository at bit.ly/2QCgZIp.

Runtime Identifiers

RIDs indicate the supported platforms for creating self-contained deployments for platforms other than Windows. For example, Windows 10 supports x86, x64, ARM and ARM64. Support for ARM addresses Windows 10 Internet of Things (IoT) devices. On Linux, only x64 is supported, with the following distributions: Ubuntu, RedHat, CentOS, Fedora, Debian, Gentoo, OpenSuse, Oracle, Tizen and LinuxMint. In macOS, versions 10.10 through 10.13 are supported. Finally, Android is supported in .NET Core 2.0 or later, which also introduces a portable RID that will build all options for a particular target group. More information can be found in the RID catalog located at bit.ly/2PKXRXi.

CoreRT Deployments

In addition to FDD and SCD, a third option being worked on at Microsoft, called CoreRT, offers the ability to generate native binaries from .NET Core-based code. CoreRT performs ahead-of-time (AoT) compilation using the CoreCLR just-in-time (JIT) compiler. This allows .NET Core code to produce both single executables and libraries that can be consumed by other languages like C++. CoreRT lets .NET developers create libraries and executables that are native to the targeted platform, providing a wider reach for the .NET platform.

Getting started with CoreRT is as simple as adding a NuGet package to a project. Inside the sample project I’ve been working with, I simply run the following command:

> dotnet new nuget

With the nuget.config file added, I then call to the .NET MyGet feed using the following lines:

<add key="dotnet-core"
  value="https://dotnet.myget.org/F/dotnet-core/api/v3/index.json"/>
<add key="nuget.org"
  value="https://api.nuget.org/v3/index.json" protocolVersion="3"/>

Next, I add the CoreRT NuGet package, like so:

> dotnet add package Microsoft.DotNet.ILCompiler -v 1.0.0-alpha-*

And then I run publish, passing in the platform RID, as shown here:

> dotnet publish -r win-x64

Finally, I can test the application as a natively compiled application. The application is created in a folder called native and is roughly 6KB in size, which is close to the size of the FDD application and doesn’t require any runtime to be shipped with it, as it would using SCD.

The pros of CoreRT are native compilation to a single native binary, with dependencies included, for each target. Execution is faster, because JIT compilation isn’t required, and the application should have faster throughput due to compiler optimizations for each target.

The cons are that the application must be compiled for each target platform, which needs to be selected in advance. There’s also, for now, the small matter of CoreRT being in pre-release stage.

CoreRT applications work very similarly to how SCD applications work. There’s a small native execution engine, the CoreRT native runtime, which provides runtime services like garbage collection that gets compiled with the application into a single native package. And there’s a managed portion of CoreRT that’s written in C#. The .NET Core application is first compiled with the Roslyn Compiler, then the application, along with CoreRT and CoreFX, is passed through an intermediate language compiler that analyzes the dependencies and shakes the tree so only the absolute minimum number of libraries will be compiled into native code using a compiler based on LLVM. Finally, a linker is used to link the CoreRT native runtime with the compiled native output from the application to produce the final native executable. Matt Warren has an awesome blog post about the subject at bit.ly/2NRS5pj, and of course the GitHub repository for CoreRT has links to parts of the design at github.com/dotnet/corert.

Wrapping Up

SCD offers one big advantage over FDD—it doesn’t require the user to install any additional software to support an installed application. I’m used to just building applications for Windows where the .NET framework is typically already available. But when .NET Framework isn’t installed, or the wrong version is installed, it can create bad experiences for users.

.NET Core promises to change this, as it will require users to install not only the runtime but a specific version of the runtime to support your application. SCDs may produce larger applications (and, therefore, larger downloads) because the runtime is packaged with the application, but this allows the user to install the application without having to worry about additional requirements. For users outside of Windows, such as those for macOS and Linux, SCD is the common experience that users expect, and it will help with the adoption. In environments that are controlled by the developer or organization, this becomes less of an issue, and FDD would likely have the advantage.

CoreRT—compile to native—deployments are still in the very early stages. These deployments will offer many of the advantages of both FDD and SCD, with no framework installation required and compact application files. However, there’s still a journey ahead to make this approach functional.


Jamie Phillips is a senior software development engineer at SentryOne, located in East Tennessee. He’s been working with .NET since 2007 and has a keen interest in DevOps and the cloud. He can be found on Twitter: @phillipsj73, his blog at phillipsj.net and GitHub as phillipsj.

Thanks to the following technical experts for reviewing this article: Andrew Hall (Microsoft), Daniel Oliver, Cameron Presley (SentryOne)
Andrew is the Program Manager lead for the .NET, Web and Azure App Service tools in Visual Studio. After graduating from college, he wrote line-of-business applications before returning to school for this Master’s degree in computer science. He then joined the diagnostics team in Visual Studio working on debugging, profiling and code analysis tools. He now works in the .NET and Web tools team, where he leads the team of program managers responsible for the core .NET development experiences, including projects, IntelliSense, productivity, and Azure tooling support.

Cameron Presley is a Microsoft MVP helping developers improve one day at a time.

Daniel Oliver is a software developer in Tennessee learning and building new things using .NET and Azure



Discuss this article in the MSDN Magazine forum