Link Containerized DotNet Core Libraries During Runtime
A look at how to Package DotNet Core Libraries in a Docker Base Image to Link to Dynamically
In my quest to learn more about Docker and containerization I asked myself whether it was possible to package a dotnet core class library in a container and use it from projects in other containers.
This article takes a closer look into how that can be done by using:
- A dotnet core class library that I want to containerize - mylib
- A dotnet core console application that includes mylib as a reference assembly - myconsole
- A dotnet core AspNet Api that includes mylib as a reference assembly - myapi
I use Visual Studio Code, Docker Desktop Community Edition 2.4.0.0 on Mac OS X 10.15.
MyLib - Jill
Mylib provides a class called ClassLib:

After building the library it is published in a lib
folder under the root folder where my solution resides. To keep things simple, I have a single root folder containing mylib, myconsole
and myapi
. In reality, these could be separate solutions.

To package the library in a base image the following Dockerfile is used:

To create a base docker image that does not depend on any other docker images it starts FROM scratch
. It then creates an /app
directory in the image and sets it as the working directory. The COPY /lib .
command copies all the files from the /lib
folder where the mylib
library was published into the working directory /app
in the image.
The Docker Image build for the s/lib
image is as follows:

The output of the mylib
project is all that this image contains. It has no dependencies on any other images.
MyConsole - Jack
The first project to use mylib
is myconsole
:

It uses mylib.dll
as a reference assembly from under the lib
folder:

The HintPath tells it where to look for the mylib.dll
during runtime. This is set to look for the reference assembly in the same folder where myconsole
runs from. I have explicitly commented out the <Private>false</Private>
tag since we need this to create the myconsole.deps.json dependency file properly. Setting <Private>true</Private>
creates a dependency file without the mylib
as a dependency which leads to a runtime failure even when the mylib.dll
is present in the same folder and myconsole
:

Another way to handle the issue would be to manually modify the myconsole.deps.json
file to include the dependency which I choose not to in order to keep this article simpler.
Since the whole point of this article is to re-use a reference library from a docker container, during the creation of the console
image we are going to explicitly only copy the myconsole* items into the image and ignore the mylib* items.
The myconsole
items are published in the console
folder:

The Docker Image file to create an s/console
image is as follows:

Here we start by referencing the s/lib
image as library
. Since console
is a dotnet core application, it requires the dotnet/core/runtime
to run - which is the basis of this image. All the files published under /app/
folder of the base s/lib
image are then copied into the working directly /app
of this image. Only the files starting with myconsole*
published by building myconsole
are then copied into the /app
directory. This allows myconsole
to pick up the mylib.dll
reference assembly (copied in the previous step) during runtime. To show that we are only copying the files starting with myconsole*
, we copy the same batch under a /app/consoleonly/
directory. We are going to check this folder later in the article.
Let’s build the s/console
image:

and run it:

My, my!! Jill and Jack are in love 💏 .
Let’s start the container again and go and have a peek inside:

and as expected, only the items starting with myconsole*
has indeed been copied into the consoleonly
directory. This indicates that the mylib*
items in the app
directory are from the base s/lib
library-only image.
MyApi - Jane
The second project to use mylib
is myapi
:

It also uses mylib.dll
as a reference assembly in the exact same way as myconsole
. The myapi
items are published in the api
folder:

The Docker Image file to create s/api
is almost exactly like the one to create s/console
differing only in the inclusion of the dotnet/core/aspnet
image:

Building and running the container:

The web api is published on port 5000 and navigating to it shows:

Well, well… Jill and Jane are in love 👩❤️💋👩 . Wait … What happened to Jack❗
What happened to Jack?
Let’s see what we did so far.
- We have created a dotnet core library and a base docker image that contains the library
- We have built two different kind of dotnet projects - console and webapi - that requires the dotnet library during the build process
- We have created a docker images for each of the dotnet projects that use the library but have explicitly made sure that the library being used is not the ones that these projects have built against. The library being used is the one that comes from the base docker image in step 1.
Step 3 feels very awkward. What is the point of using the library from the base image instead of keeping the dependency for each project during the image creation? Well, let’s see what the point really is.
Here is the list of Docker images that we have create so far:

s/lib:latest
— contains the class librarys/console:latest
— contains the class library +dotnet/core/runtime
s/api:latest
— contains the class library +dotnet/core/aspnet
Let us now go ahead and change the code in the library being shared and create a new version of the image s/lib:2.0
:

Now, without rebuilding our console or api projects, let’s just create a new version of their corresponding images using s/lib:2.0
as the base image:

If we now run the console 2.0 container:

Well… Well… It looks like Jack has a new girlfriend named Amy ❤️ .
And running the api 2.0 container we see:

Alas! Our story does not end well for Jack and Jill 💔 . Will they find each other again?
Conclusion
I had a great time learning how to package a library in a docker image so that it can be used by other images. Of course, we have to keep in mind that only trusted docker images should be used — especially in this case since we are dynamically binding to the assembly. Moreover, I haven’t used any versioning in this article but in production, it is extremely important.
I hope you will have as much fun as I did learning about containers 😃. As always you will find all the code for this article in my GitHub Repo.