The newly released dotnet 3 preview 6 has an interesting feature called publish single file where an application can be published as a self contained app that will extract and run on the platform it was compiled for.

In order to add this feature in our .csproj, add the following lines:

<PropertyGroup>
  <TargetFramework>netcoreapp3.0</TargetFramework>
  <PublishTrimmed>true</PublishTrimmed>
  <PublishReadyToRun>true</PublishReadyToRun>
  <PublishSingleFile>true</PublishSingleFile>
  <CrossGenDuringPublish>false</CrossGenDuringPublish>
</PropertyGroup>

There are some flags that may sound weird, let’s go through them:

  • PublishTrimmed indicates to remove all the dotnet dependencies that are not being used by our code. When doing this in a real world application, it is very important to test it afterwards in case some dependency that is referenced by code (for example using reflection) is being trimmed as well.
  • PublishReadyToRun it is supposed to make the start time of our application faster, but in the process it will make the binary heavier.
  • CrossGenDuringPublish is a workaround that needs to be present for indicating the machine that is running the publish, that it should not bother with compilation for other RID (more about this below)

After these flags had been set, the application is runnable using:

dotnet publish -r osx-x64 -c Release -o ./deploy
./deploy/<APP_NAME> <- Execute the application

When publishing a single file, don’t forget to add the runtime identifier catalog (RID) corresponding to the platform where the application is going to be run. For example: -r osx-x64

With the basics of this feature already covered, let’s move to the important bit.

Deploy application on an Alpine based container

One important win using this approach is that the published binary does not need to be run in a container that has dotnet runtime installed. But one caveat for consideration is that those internal dependencies used under the hood by dotnet are still needed. There are two options:

Use plain alpine and install dotnet internal dependencies:

FROM alpine
RUN apk update && apk add libstdc++ && apk add libintl

Use runtime-deps alpine image from Microsoft:

This image contains the native dependencies needed by .NET Core. It does not include .NET Core. It is for self-contained applications.

FROM mcr.microsoft.com/dotnet/core/runtime-deps:3.0.0-preview6-alpine3.9

With that being said, the next is straightforward:

FROM mcr.microsoft.com/dotnet/core/sdk:3.0.100-preview6-alpine3.9 AS build

WORKDIR /app

COPY . .

RUN dotnet publish -r linux-musl-x64 -c Release -o ./deploy

FROM mcr.microsoft.com/dotnet/core/runtime-deps:3.0.0-preview6-alpine3.9

COPY --from=build /app/deploy ./app

ENV ASPNETCORE_URLS http://*:5000

EXPOSE 5000

ENTRYPOINT ["./app/<APP_NAME>"]

Basically the Dockerfile is publishing the application (as a binary) on an alpine container with dotnet sdk installed. This is important because the builder has to use the same architecture than the runner. It is not possible to build a linux-musk-x64 binary on a windows container.

Then the result files (the binary and a single DLL) are copied to an alpine container only having dotnet runtime dependencies.

At the end the ENTRYPOINT line is executed. Check that this one is not doing dotnet app/<APP_NAME> but just ./app/<APP_NAME>

I did a sample API with this approach, which can be found my github. The container weighted 91MB, used 30MB RAM and 0.08% CPU. Awesome, right?