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:
PublishTrimmedindicates 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.
PublishReadyToRunit is supposed to make the start time of our application faster, but in the process it will make the binary heavier.
CrossGenDuringPublishis 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:
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
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.
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>"]
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
I did a sample API with this approach, which can be found my github. The container weighted
30MB RAM and
0.08% CPU. Awesome, right?