Install .Net Core 3.1 SDK
.Net Core 3.0 added many more features along with option to publish trimmed self-contained applications. However, 3.0 version is already declared EOL (End of Life). Hence we will use 3.1 which is designated as LTS (Long Term Support) release.
Depending on the version of Ubuntu that you have, choose corresponding commands to run.
Run below commands to install .Net Core SDK 3.1 on Ubuntu 16.04:
{
wget https://packages.microsoft.com/config/ubuntu/16.04/packages-microsoft-prod.deb -O packages-microsoft-prod.deb
sudo dpkg -i packages-microsoft-prod.deb
sudo apt-get update
sudo apt-get install -y apt-transport-https
sudo apt-get update
sudo apt-get install -y dotnet-sdk-3.1
}
Run below commands to install .Net core SDK 3.1 on Ubuntu 18.04:
{
wget https://packages.microsoft.com/config/ubuntu/18.04/packages-microsoft-prod.deb -O packages-microsoft-prod.deb
sudo dpkg -i packages-microsoft-prod.deb
sudo apt-get update
sudo apt-get install -y apt-transport-https
sudo apt-get update
sudo apt-get install -y dotnet-sdk-3.1
}
Create new console application
Run below command to create new console application with name hellodotnet31
:
$ dotnet new console -o hellodotnet31
# Output
The template "Console Application" was created successfully.
Processing post-creation actions...
Running 'dotnet restore' on hellodotnet31/hellodotnet31.csproj...
Determining projects to restore...
Restored /home/hellodotnet31/hellodotnet31.csproj (in 327 ms).
Restore succeeded.
A new directory hellodotnet31
will be created with default Console Application template.
Edit hellodotnet31/Program.cs
and update message from Hello World!
to Hello .Net Core 3.1!
.
Difference between .Net Core 2.1 and 3.1 appliction is the target framework that is defined. You are allowed to publish trimmed self-contained applications with target framework 3.0 onward only.
Publishing trimmed self-contained application
.Net Core 3.0 adds two new options for creating a single file trimmed binary of the project. It further reduces the size of the binary significantly.
So we are adding /p:PublishTrimmed=true
and /p:PublishSingleFile=true
to our command for publishing the application like below:
$ dotnet publish --configuration Release --self-contained true --runtime linux-musl-x64 /p:PublishTrimmed=true /p:PublishSingleFile=true
# Output
Microsoft (R) Build Engine version 16.6.0+5ff7b0c9e for .NET Core
Copyright (C) Microsoft Corporation. All rights reserved.
Determining projects to restore...
Restored /home/hellodotnet31/hellodotnet31.csproj (in 422 ms).
hellodotnet31 -> /home/hellodotnet31/bin/Release/netcoreapp3.1/linux-musl-x64/hellodotnet31.dll
Optimizing assemblies for size, which may change the behavior of the app. Be sure to test after publishing. See: https://aka.ms/dotnet-illink
hellodotnet31 -> /home/hellodotnet31/bin/Release/netcoreapp3.1/linux-musl-x64/publish/
If we take a look at the output under bin/Release/netcoreapp3.1/linux-musl-x64/publish/
, we will find a single executable hellodotnet31
and its PDB file. The size of this binary is very small (around 35 MB) compared to without these two options (around 76 MB). So size has reduced by over 50%!!!
We are adding /p:PublishTrimmed=true
and /p:PublishSingleFile=true
to our multi-stage Dockerfile as well.
Along with it .Net Core 3.1 further reduced runtime dependencies as well. Hence, we need to use less no. of packages in Alpine than 2.1. Please compare https://github.com/dotnet/dotnet-docker/blob/master/src/runtime-deps/2.1/alpine3.12/amd64/Dockerfile and https://github.com/dotnet/dotnet-docker/blob/master/src/runtime-deps/3.1/alpine3.12/amd64/Dockerfile
Final Dockerfile contents:
# Create application build
FROM mcr.microsoft.com/dotnet/core/sdk:3.1 AS build-env
WORKDIR /source
COPY *.csproj ./
RUN dotnet restore
COPY . ./
RUN dotnet publish --configuration Release --self-contained true --runtime linux-musl-x64 /p:PublishTrimmed=true /p:PublishSingleFile=true
# Create application image
FROM amd64/alpine:3.12
RUN apk add --no-cache \
ca-certificates \
# .NET Core dependencies
krb5-libs libgcc libintl libssl1.1 zlib libstdc++
# Enable detection of running in a container
ENV DOTNET_RUNNING_IN_CONTAINER=true
# Set the invariant mode since icu_libs isn't included (see https://github.com/dotnet/announcements/issues/20)
ENV DOTNET_SYSTEM_GLOBALIZATION_INVARIANT=true
WORKDIR /app
COPY --from=build-env /source/bin/Release/netcoreapp3.1/linux-musl-x64/publish/ .
ENTRYPOINT ["./hellodotnet31"]
Create a .dockerignore with content like below:
[Bb]in/
[Oo]bj/
Dockerfile
*.pdb
We can run docker build command like below:
docker build -t hellodotnet31 .
Sample Output:
Sending build context to Docker daemon 167MB
Step 1/13 : FROM mcr.microsoft.com/dotnet/core/sdk:3.1 AS build-env
---> 006ded9ddf29
Step 2/13 : WORKDIR /source
---> Using cache
---> 48b752b4b167
Step 3/13 : COPY *.csproj ./
---> 5b9171db8d71
Step 4/13 : RUN dotnet restore
---> Running in 641e02115d5d
Determining projects to restore...
Restored /source/hellodotnet31.csproj (in 322 ms).
Removing intermediate container 641e02115d5d
---> 041b5ed42fc4
Step 5/13 : COPY . ./
---> 2d27db689248
Step 6/13 : RUN dotnet publish --configuration Release --self-contained true --runtime linux-musl-x64 /p:PublishTrimmed=true /p:PublishSingleFile=true
---> Running in 9d0c64867b3d
Microsoft (R) Build Engine version 16.6.0+5ff7b0c9e for .NET Core
Copyright (C) Microsoft Corporation. All rights reserved.
Determining projects to restore...
Restored /source/hellodotnet31.csproj (in 13.66 sec).
hellodotnet31 -> /source/bin/Release/netcoreapp3.1/linux-musl-x64/hellodotnet31.dll
Optimizing assemblies for size, which may change the behavior of the app. Be sure to test after publishing. See: https://aka.ms/dotnet-illink
hellodotnet31 -> /source/bin/Release/netcoreapp3.1/linux-musl-x64/publish/
Removing intermediate container 9d0c64867b3d
---> 9428e5669d17
Step 7/13 : FROM amd64/alpine:3.12
---> a24bb4013296
Step 8/13 : RUN apk add --no-cache ca-certificates krb5-libs libgcc libintl libssl1.1 zlib libstdc++
---> Using cache
---> 71bde714c50d
Step 9/13 : ENV DOTNET_RUNNING_IN_CONTAINER=true
---> Using cache
---> cbe1a5d7a5cd
Step 10/13 : ENV DOTNET_SYSTEM_GLOBALIZATION_INVARIANT=true
---> Using cache
---> 974aaaa299b8
Step 11/13 : WORKDIR /app
---> Using cache
---> 8795eae99175
Step 12/13 : COPY --from=build-env /source/bin/Release/netcoreapp3.1/linux-musl-x64/publish/ .
---> 501b65cab1dd
Step 13/13 : ENTRYPOINT ["./hellodotnet31"]
---> Running in 8cd1b6dc3e4e
Removing intermediate container 8cd1b6dc3e4e
---> 65bcaa6a7313
Successfully built 65bcaa6a7313
Successfully tagged hellodotnet31:latest
We can verify that the hellodotnet31
application runs sucessfully using below command:
$ docker run --name hellodotnet31 hellodotnet31
# Output
Hello .Net Core 3.1!
Size comparison (3.1 vs 2.1)
$ docker images
# Output
REPOSITORY TAG IMAGE ID CREATED SIZE
hellodotnet31 latest 8a4e2ecbf5f8 3 minutes ago 46.4MB
When we compare it with earlier self-trimmed image we had created with .Net Core 2.1 using alpine:3.12 as base image, we see that new image is just 46.4 MB in size vs 86.4 MB earlier.