How can I target multiple .NET Core versions from a single Docker image?

Viewed 2194

I have a library that targets .NET Standard 2.0. To verify compatibility, I would like to run my unit tests with the current and long term support (LTS) versions of .NET Core. When this question was written, those were:

Target Framework Target framework moniker (TFM)
.NET 5.0 (current) net5.0
.NET Core 3.1 (LTS) netcoreapp3.1
.NET Core 2.1 (LTS) netcoreapp2.1

It's easy enough to set up the csproj files to target multiple frameworks:

<PropertyGroup>
  <TargetFrameworks>netcoreapp2.1;netcoreapp3.1;net5.0</TargetFrameworks>
</PropertyGroup>

That works well for local builds when all of these SDKs are installed. However, I'd prefer to have my CI builds take place on a single, lightweight Docker container. I'm not worried about verifying .NET Framework support, so I don't need Windows. The .NET SDK images on Docker Hub work well for any single framework (i.e., mcr.microsoft.com/dotnet/sdk:5.0), but I would like to find a Docker image that includes all three of these SDKs without the baggage of a "kitchen sink" image like the GitHub-hosted runners, which include many unrelated frameworks and tools.

I could write my own Dockerfile, starting from a base image and scripting the installation of the extra SDKs, but surely I can't be the only person who could use something like this. Does a suitable Docker image already exist somewhere? Should I be taking a different approach to this problem, like scripting the SDK installation with setup-dotnet or an equivalent?

3 Answers

While the latest dotnet SDK can target any previous SDK versions,dotnet test requires the particular dotnet runtime. There are many ways to install dotnet sdk/runtimes into a base docker image, but the easiest is to just copy the SDK and/or runtimes from existing base images. Here's an example for dotnet 6, should you need the dotnet 5.0 runtime for unit tests:

FROM mcr.microsoft.com/dotnet/sdk:6.0 as build

#uncomment if you need the 5.0 SDK also (unlikely):
#COPY --from=mcr.microsoft.com/dotnet/sdk:5.0 /usr/share/dotnet/sdk /usr/share/dotnet/sdk

# "install" the dotnet 5 runtime
COPY --from=mcr.microsoft.com/dotnet/sdk:5.0 /usr/share/dotnet/shared /usr/share/dotnet/shared

If you run the above image and get into the shell and do dotnet --info it's as expected:

$ docker run -it --rm 0de6f2cdf814
root@b4bb146dcf7e:/src# dotnet --info
.NET SDK (reflecting any global.json):
 Version:   6.0.200
 Commit:    4c30de7899

Runtime Environment:
 OS Name:     debian
 OS Version:  11
 OS Platform: Linux
 RID:         debian.11-x64
 Base Path:   /usr/share/dotnet/sdk/6.0.200/

Host (useful for support):
  Version: 6.0.2
  Commit:  839cdfb0ec

.NET SDKs installed:
  6.0.200 [/usr/share/dotnet/sdk]

.NET runtimes installed:
  Microsoft.AspNetCore.App 5.0.10 [/usr/share/dotnet/shared/Microsoft.AspNetCore.App]
  Microsoft.AspNetCore.App 6.0.2 [/usr/share/dotnet/shared/Microsoft.AspNetCore.App]
  Microsoft.NETCore.App 5.0.10 [/usr/share/dotnet/shared/Microsoft.NETCore.App]
  Microsoft.NETCore.App 6.0.2 [/usr/share/dotnet/shared/Microsoft.NETCore.App]

To install additional .NET runtimes or SDKs:
  https://aka.ms/dotnet-download
root@b4bb146dcf7e:/src# 

Just use the Docker image for the latest .NET version (currently mcr.microsoft.com/dotnet/sdk:5.0). It's capable of building projects that target different versions. It's the same as how you can do this locally on your dev machine. You can just install 5.0 SDK on your machine and build projects that target 2.1, 3.1, or 5.0.

If you're wanting to run unit tests in the same container, you will need to install the extra runtimes that you require for each of the versions other than 5.0. This will vary based on what OS you're targeting but you can find details on the installation at https://docs.microsoft.com/en-us/dotnet/core/install/. For example, on Debian, you can install ASP.NET 3.1 runtime with the following:

wget https://packages.microsoft.com/config/debian/10/packages-microsoft-prod.deb -O packages-microsoft-prod.deb && \
dpkg -i packages-microsoft-prod.deb && \
apt-get update && \
apt-get install -y apt-transport-https && \
apt-get update && \
apt-get install -y aspnetcore-runtime-3.1

I've tried to avoid building custom images and scripting extra runtimes, so my variant was to run build/test on multiple containers (sdk:2.1, sdk:3.1) separately.

Funny enough, having multitarget:

<PropertyGroup>
  <TargetFrameworks>netcoreapp2.1;netcoreapp3.1</TargetFrameworks>
</PropertyGroup>

does not allow building on sdk:2.1 container, even if you specify that you only want to build for 2.1, with parameter -f netcoreapp2.1.

I ended up with conditioned csproj file, which specifies TargetFrameworks based on outside parameter. For example, my csproj configuration is:

<TargetFrameworks Condition=" '$(TestTargetFramework)' == '' ">netstandard2.0;netstandard2.1</TargetFrameworks>
<TargetFrameworks Condition=" '$(TestTargetFramework)' == 'netcoreapp2.1' ">netstandard2.0</TargetFrameworks>
<TargetFrameworks Condition=" '$(TestTargetFramework)' == 'netcoreapp3.1' ">netstandard2.1</TargetFrameworks>

And I can build/test it on sdk:2.1:

dotnet test ./test/Tests --configuration Release /p:TestTargetFramework=netcoreapp2.1

Or sdk:3.1:

dotnet test ./test/Tests --configuration Release /p:TestTargetFramework=netcoreapp3.1
Related