Contents

'Multi-Image' Docker Image

Problem

I was on the lookout for a solution to a problem in Docker that needed to do the following:

Create a Docker Image for a bunch of compiled ASP.NET .dll files and try reducing the container footprint

Sounds simple, right? But it turned out to be a bit more messy than I imagined. To top it off, I have NEVER even programmed in .NET so I was feeling like a visually impaired person made to navigate his way through a dark room with furniture scattered all over!

Thousand Leagues under the Container Sea!

Keeping the whole Maritime theme alive with Docker (and Kubernetes), I jumped into the sea of Docker Hub with millions of containers and found out that Microsoft hosts all the .NET related container images as .NET by Microsoft registry.

Gentle reminder that I have never even thought of .NET before in my life, so just trying to figure out whether I need an one or more of the following images:

  • SDK
  • ASP.NET Core Runtime
  • .NET Runtime
  • .NET Runtime Dependencies
  • .NET Monitor Tool
  • .NET Samples

was a side-mission in its own right.

Side-Mission

A quick deep-dive into Microsoft’s Docker .NET Docs made me think I only need the following docker image: mcr.microsoft.com/dotnet/aspnet:6.0

So packing everything in a simple Dockerfile


FROM mcr.microsoft.com/dotnet/aspnet:6.0-focal


WORKDIR /DotNetVoyage

COPY ./appsettings.json /DotNetVoyage/Server-Files/

# Copy every '.dll' file I was handed 
COPY ./* /DotNetVoyage/Server-Files/
CMD [ "dotnet", "Server-Files/Server.dll" ]

Building it locally and accessing the App initially on the dedicated ports seemed to work just fine and I thought I was getting off work a bit early today. I jinxed myself.

Upon some API testing of the image I started seems some logs that were strange to my non .NETian brain.

The specified framework can be found at:
It was not possible to find any compatible framework version
The framework 'Microsoft.AspNetCore.App', version '5.0.0' (x64) was not found.
 - The following frameworks were found:
    6.0.6 at [/usr/share/dotnet/shared/Microsoft.AspNetCore.App]
You can resolve the problem by installing the specified framework and/or SDK.

I was head scratching as to how I can make my runtime which is 6.0.0 potentially have an SDK version that must have 5.0.0 SDK on it. It got worse, I found out I needed the now defunct and not supported 2.1 version of the SDK too!

Build everything from scratch?

Unaware if there was a way to introduce the different SDKs into the already slimmed container image, I had lost hope and decided to build an image with maybe debian or ubuntu or alpine.

However, upon asking the Slack Community and rummaging through some StackExchange queries, I traced out a plan that was pretty familiar to me: Using Multi-Stage Docker Builds.

Charting towards a Destination

I use Multi-Stage Docker builds almost everyday for work and so I decided to design my container image as follows:

Stage 1: pull SDK 2.1
  1.1: find out which directory do the SDK Files persist

Stage 2: pull SDK 5.1
  2.1: find out which directory do the SDK Files persist

Stage Prod: Pull the 6.0.0 Runtime
  Prod.1: copy all the SDK Files (from Stage 1 and Stage 2) to the same directory here in Prod Stage
  Prod.2: copy all the DLL files from host and run the dedicated DLL

Where art thou SDKs?

A quick pull:

docker pull mcr.microsoft.com/dotnet/sdk:2.1

and check within the container:

docker run -it --rm --name=sdk21-check mcr.microsoft.com/dotnet/sdk:2.1 dotnet --list-sdks

resulted in:

2.1.818 [/usr/share/dotnet/sdk]

Gotcha! the directory for the SDKs is /usr/share/dotnet/

Multi-Image Image

what I mean by this is whether Docker is smart enough to directly pull an image either using COPY or ADD instructions without me having to create Stage 1 and Stage 2 using syntax such as:

FROM mcr.microsoft.com/dotnet/sdk:2.1 as base21

FROM mcr.microsoft.com/dotnet/sdk:5.0 as base50

It turns out that COPY is extremely flexible! with the COPY --from instruction in the Dockerfile is capable of pulling the dedicated image from a registry and the respective directories can be copied easily!

Solution

Final Checks for Dockerfile

FROM mcr.microsoft.com/dotnet/aspnet:6.0-focal

# Add those missing SDKs here
## Syntax: COPY --from=<registry/image:version> image_directory ProdStage__dest_directory
COPY --from=mcr.microsoft.com/dotnet/sdk:2.1 /usr/share/dotnet /usr/share/dotnet/
COPY --from=mcr.microsoft.com/dotnet/sdk:5.1 /usr/share/dotnet /usr/share/dotnet/

# List all the SDKs / Runtimes available in the container
CMD ["dotnet", "--list-sdks", "dotnet", "--list-runtimes"]

build the image and run it and the output will be similar to:

Microsoft.AspNetCore.All 2.1.30 [/usr/share/dotnet/shared/Microsoft.AspNetCore.All]
Microsoft.AspNetCore.App 2.1.30 [/usr/share/dotnet/shared/Microsoft.AspNetCore.App]
Microsoft.AspNetCore.App 5.0.17 [/usr/share/dotnet/shared/Microsoft.AspNetCore.App]
Microsoft.AspNetCore.App 6.0.6 [/usr/share/dotnet/shared/Microsoft.AspNetCore.App]
Microsoft.NETCore.App 2.1.30 [/usr/share/dotnet/shared/Microsoft.NETCore.App]
Microsoft.NETCore.App 5.0.17 [/usr/share/dotnet/shared/Microsoft.NETCore.App]
Microsoft.NETCore.App 6.0.6 [/usr/share/dotnet/shared/Microsoft.NETCore.App]

Voilá ! Runtimes v6.0, v5.0, v2.1 all in the same container !!

Final Dockerfile

FROM mcr.microsoft.com/dotnet/aspnet:6.0-focal

# Add those missing SDKs here
## Syntax: COPY --from=<registry/image:version> image_directory ProdStage__dest_directory
COPY --from=mcr.microsoft.com/dotnet/sdk:2.1 /usr/share/dotnet /usr/share/dotnet/
COPY --from=mcr.microsoft.com/dotnet/sdk:5.1 /usr/share/dotnet /usr/share/dotnet

WORKDIR /DotNetVoyage

COPY ./appsettings.json /DotNetVoyage/Server-Files/

# Copy every '.dll' file I was handed 
COPY ./* /DotNetVoyage/Server-Files/
CMD [ "dotnet", "Server-Files/Server.dll" ]

Just another day on the decks with Docker !!

Get in touch if you feel like dropping some suggestions, feedbacks or criticism! Always happy to learn and improve!