Azure Artifacts allows you to host your own NuGet-feed, which is great, but it introduces a slight complexity in the form of authentication and building Docker images.

Azure DevOps' tasks for restoring packages will handle this for you as well as dotnet restore locally will re-use your own credentials. However, when running inside a Docker build session, these credentials are missing and rightly so: You don't want those as part of the image for obvious security reasons.

Introducing our friend, the environment variable named VSS_NUGET_EXTERNAL_FEED_ENDPOINTS.

This variable is expected to have the configuration of, as the name suggests, various feed endpoints and how to authenticate to them. dotnet restore will automatically read and use this when restoring packages.

The format looks like this:

{
  "endpointCredentials": [
    {
      "endpoint": "<azure artifact feed endpoint>",
      "username": "optional",
      "password": "<access token>"
    }
  ]
}

To get this token, you can either connect to the feed like this or create a Personal Access Token by yourself. Just remember not to store this in source control.

If you use the NuGet credential provider, the token is created for you and stored here:

  • Windows: $env:UserProfile\AppData\Local\MicrosoftCredentialProvider
  • Linux: $HOME/.local/share/MicrosoftCredentialProvider/

Now that we have a way to authenticate, we need to connect this to our Docker build in a proper way. We can do this by specifying a build argument for the Dockerfile which will keep the token out of the Docker image and only exist during the build.

Because we will be using Docker multi-stage build, we will only put this configuration into the image that will be used for build - reassuring ourselves that nothing will end up in the final image.

We already know which environment variable dotnet restore is looking for, so lets hook that up:

ARG NUGET_FEED
ENV VSS_NUGET_EXTERNAL_FEED_ENDPOINTS=$NUGET_FEED

By using the command line argument --build-arg we can now feed our configuration into the image during the build process.

We also need to make sure the credential provider is installed, so we add that to the Dockerfile as well.

RUN wget https://raw.githubusercontent.com/Microsoft/artifacts-credprovider/master/helpers/installcredprovider.sh \
    && chmod +x installcredprovider.sh \
    && ./installcredprovider.sh

The entire Dockerfile could look something like this:

FROM microsoft/dotnet:2.2-aspnetcore-runtime AS base
WORKDIR /app

FROM microsoft/dotnet:2.2-sdk AS build
RUN wget https://raw.githubusercontent.com/Microsoft/artifacts-credprovider/master/helpers/installcredprovider.sh \
    && chmod +x installcredprovider.sh \
    && ./installcredprovider.sh
ARG NUGET_FEED
ENV VSS_NUGET_EXTERNAL_FEED_ENDPOINTS=$NUGET_FEED
WORKDIR /src
COPY nuget.config .
COPY *.csproj .
RUN dotnet restore
COPY . .
WORKDIR /src
RUN dotnet build -c Release -o /app MyProject.csproj

FROM build AS publish
RUN dotnet publish -c Release -o /app MyProject.csproj

FROM base AS final
EXPOSE 500
ENV ASPNETCORE_URLS=http://+:5000
WORKDIR /app
COPY --from=publish /app .
ENTRYPOINT [ "dotnet", "MyProject.dll" ]

The way you now build this image and making use of the build argument, is kind of up to you depending on how you acquired the token and stored it.

In my case, I had already connected to the feed, so the token was stored in a local cache file - all I had to do was extract it and put it into the format we talked about earlier.

export NUGET_FEED=$(cat $HOME/.local/share/MicrosoftCredentialProvider/SessionTokenCache.dat | jq -c -r '. | to_entries[] | {"endpointCredentials": [{"endpoint": .key, "username":"optional","password":.value}]}'
docker build --build-arg NUGET_FEED=$NUGET_FEED -t MyProject .

Bit of a mouthful, I'll grant you that - but that's what scripts are for :)
When combined with a Makefile (like I wrote about here) this becomes nicely transparent and easy.