3 minutes
NET Core 3.0 - Publish single file binary on alpine container
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?