Random things of a random world

Sami's Page

  • Join Us on Facebook!
  • Follow Us on Twitter!
  • LinkedIn
  • Subcribe to Our RSS Feed

Building .NET Core App in Docker with Jenkins and NuGet Caching

I wanted to put the build system for my .NET Core backends into Docker with Jenkins to make sure the build environment is nice and clean and repeatable every time. It seems it's not as simple as it should be. I wanted to keep the building simple, and I wanted to keep the stages, so I didn't want to put everything about the build to a Dockerfile. I want to make a Jenkins pipeline and have pretty stages.

Running the mcr.microsoft.com/dotnet/sdk:5.0 container is simple. Running stuff inside it is simple. But then the user permissions, caches etc aren't such a simple thing at all.

Jenkins runs everything in the containers as jenkins by default. That's good. I wanted to bind the build directory into the container so I don't need to copy files from the temporary container anywhere. This way the results are also owned by jenkins and there's no permission issues. But the container isn't designed to be run directly as a non-root user. It defaaults to writing to /.dotnet etc. Not good.

So, what you need to do is to set DOTNET_CLI_HOME environment variable to somewhere writable, like /tmp/dotnet. After that things work.

Now, I also want a cache. Restoring packages takes a long time. Maybe longer than the actual build. Since the containers are ephemeral there's no caching of them. So I want to use an external cache. Luckily there's an environment variable for that too: NUGET_PACKAGES. So let's just set that to a suitable place and bind the path to the container. And of course make sure jenkins user has permissions.

So in the end what I did is this:

stage('Build Backend') {
  steps {
    script {
      docker.image('mcr.microsoft.com/dotnet/sdk:5.0').inside('-v ${WORKSPACE}:/src  -v /var/nuget/:/tmp/nuget/ -e NUGET_PACKAGES=/tmp/nuget/packages -e DOTNET_CLI_HOME=/tmp/dotnet') { c -> 
        sh 'cd /src; dotnet publish -c Release -o app'

 That simple, when finally got it right. It does this:

  • mount the Jenkins workspace as /src in the container
  • mount /var/nuget (the nuget cache in the host machine) as /tmp/nuget/ in the container
  • tell .NET that NuGet packages are to be cached in /tmp/nuget/packages
  • tell .NET that the settings and whatnots should be put into /tmp/dotnet
  • have.NET build and publish the project into app folder

Since /tmp/dotnet doesn't exist it'll be created with the suitable permissions and no issues will come from it. After this I can just copy the results from app folder into another container through a Dockerfile in another step.

stage('Build Image') {
  steps {
    script {
      image = docker.build "sami/project-$BRANCH_NAME:$BUILD_NUMBER"

And that will nicely create a container, name it based on the branch the build was done in (there's of course dev branches etc), number it based on Jenkins build number, and also as latest for convenience.

Nice, simple, and efficient. As long as you know how it goes. Took a few tries to get it right.

Add comment