.Net Core – Creating Docker Containers in Azure DevOps using private Nuget Feeds

I recently ran into an issue where some our .Net Core apps have private nuget feed dependencies to both external and internal libraries. When you build apps in Azure DevOps, by default, access to private feeds stored in Azure DevOps already has a bearer token throughout the life of the pipeline in runtime. If you want to explore on the bearer token, it’s deeply discussed here: Predefined variables in Azure DevOps

Once a docker image has been pulled, the context of building an app within the image is restricted within the image context. To fix the issue, Microsoft has developed an azure artifacts credential provider that allows users to set security context in runtime via dotnet.exe or nuget.exe. The creds provider (shortened) is fully documented here.

Essentially, the steps involved are:

  1. Installing Credential Provider inside the docker container
  2. Setting the credentials in runtime via an environment variable within the docker container. VSS_NUGET_EXTERNAL_FEED_ENDPOINTS
  3. Passing a personal access token (PAT) created in Azure DevOps during docker build invocation

Prior to this, I was merely compiling the code outside of the container given that private feeds are authorized in the pipeline context then I simply copying the compiled bits over in the container. This, works, but… not a good pattern.

Installing Credential Provider inside the docker container

For this example, we use a nuget.config file to specify all nuget sources that hosts internal nuget packages. The .net cli (dotnet.exe) supports passing source location endpoints during execution so you don’t necessarily need to have a nuget.config file.

dotnet build --source c:\packages\mypackages

However, for ease of development, I prefer using nuget.config to specify nuget feed endpoints. This way, anyone who works on the same codebase doesn’t have to keep passing nuget source endpoints.

Linux Containers: https://github.com/microsoft/artifacts-credprovider/blob/master/helpers/installcredprovider.sh

# Docker Build Arguments
ARG PAT

RUN apt-get update && apt-get install -y locales
RUN sed -i -e 's/# en_US.UTF-8 UTF-8/en_US.UTF-8 UTF-8/' /etc/locale.gen && dpkg-reconfigure --frontend=noninteractive locales && update-locale LANG=en_US.UTF-8
ENV LANG en_US.UTF-8
ENV LC_ALL=en_US.UTF-8

RUN wget -qO- https://raw.githubusercontent.com/Microsoft/artifacts-credprovider/master/helpers/installcredprovider.sh | bash

ENV NUGET_CREDENTIALPROVIDER_SESSIONTOKENCACHE_ENABLED true
ENV VSS_NUGET_EXTERNAL_FEED_ENDPOINTS {\"endpointCredentials\": [{\"endpoint\":\"https://MyADOInstance.pkgs.visualstudio.com/_packaging/InternalNugetFeed/nuget/v3/index.json\", \"username\":\"build\", \"password\":\"${PAT}\"}]}

Windows Containers: https://github.com/microsoft/artifacts-credprovider/blob/master/helpers/installcredprovider.ps1

# Docker Build Arguments
ARG PAT

# This is specified here: https://github.com/microsoft/artifacts-credprovider
RUN xcopy "Utilities\credsprovider" "%userprofile%\.nuget\plugins" /E /I

ENV NUGET_CREDENTIALPROVIDER_SESSIONTOKENCACHE_ENABLED true
ENV VSS_NUGET_EXTERNAL_FEED_ENDPOINTS {\"endpointCredentials\": [{\"endpoint\":\"https://MyADOInstance.pkgs.visualstudio.com/_packaging/InternalNugetFeed/nuget/v3/index.json\", \"username\":\"build\", \"password\":\"${PAT}\"}]}

I’ve intentionally wrote the docker file for Windows containers to xcopy bits from my source to the image container nuget directory.  Why? All we’re doing here is simply copying the bits over to the plug-ins directory of the user profile running the build instance. This is also the same for Linux containers. The difference is that we can simply invoke the bash and powershell scripts within the container and execute during run-time.

Setting the credentials in runtime via an environment variable within the docker container

Overview of environment variables used:

NUGET_CREDENTIALPROVIDER_SESSIONTOKENCACHE_ENABLED:  Controls whether or not the session token is saved to disk. If false, the Credential Provider will prompt for auth every time.

VSS_NUGET_EXTERNAL_FEED_ENDPOINTS: Json that contains an array of service endpoints, usernames and access tokens to authenticate endpoints in nuget.config

${PAT}: is an argument variable that is passed during docker build. This is your personal access token created in Azure Devops.

{\"endpointCredentials\": [{\"endpoint\":\"https://MyADOInstance.pkgs.visualstudio.com/_packaging/InternalNugetFeed/nuget/v3/index.json\", \"username\":\"build\", \"password\":\"${PAT}\"}]}

The JSon file above sets the authentication scheme for the nuget feed endpoint specified in the nuget.config file. Ensure that both feeds match.

Passing a personal access token (PAT) created in Azure DevOps during docker build invocation

For this post, I’m utilizing new Azure DevOps pipeline capabilities. In this case, YAML pipelines. For more information, see my previous post on: YAML Builds in Azure DevOps – A Continuous Integration Scenario.

There are lots of ways to implement secure tokens in Azure DevOps. You really don’t want to expose tokens in clear text as part of your YAML file 😊. The most simplistic scenario is to declare a variable in your pipeline, encrypt it then use it within your pipeline.  See example below:

To pass the encrypted value during docker build:

docker build -f $(DockerFile) -t $(DockerImageEndpoint) ${DockerAppPath} --build-arg PAT=$(AzureDevOpsPAT)

$(AzureDevOpsPAT): Encrypted value declared at the pipeline.

A pipeline result would look something like this when implemented correctly: