Tuesday, August 04, 2020

Restoring from an Azure Artifacts NuGet feed from inside a Docker Build

If you are using Azure DevOps pipelines to automate building your .NET Core application Docker images, it's natural to also want to use the DevOps Artifacts NuGet feed for your internally hosted NuGet packages. Unfortunately there is much confusion and misinformation about how to authenticate against the Artifacts NuGet feed. While researching this topic I found various sources saying that you needed to install the NuGet credential provider as part of the docker build, and then set various environment variables. I followed this route (excerpt from an example below), even to the extent of creating a custom Docker image for all our dotnet builds with the credential provider already installed.  
ARG PAT
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://pkgs.dev.azure.com/jakob/_packaging/DockerBuilds/nuget/v3/index.json\”, \”password\”:\”${PAT}\”}]}”
The technique is to install the credential provider, then configure it with the DevOps Artifacts endpoint and a Personal Access Token (PAT), which you can generate by going to your user settings from the DevOps UI:

DevOps User Settings


After wasting over a day on this, I was then very surprised indeed to find that a colleague was restoring from the same DevOps Artifacts feed on a locally hosted TeamCity server, simply by providing the PAT as the NuGet API-Key! They hadn't installed the NuGet credential provider, so according to the Microsoft documentation it shouldn't work?

I tried it myself. The PAT does indeed work as a NuGet API-Key. A slight further complication is that the 'dotnet restore' command doesn't have an API-Key switch, so the next easiest thing is to simply use a nuget.config file as follows:

<?xml version="1.0" encoding="utf-8"?>
<configuration>

    <packageSources>
        <add key="DevOpsArtifactsFeed" value="your-devops-artifacts-nuget-source-URL" />
    </packageSources>

    <packageSourceCredentials>
        <DevOpsArtifactsFeed>
            <add key="Username" value="foo" />
            <add key="ClearTextPassword" value="your-PAT" />
        </DevOpsArtifactsFeed>
    </packageSourceCredentials>

</configuration>
Replace the place-holders with your Artifacts NuGet feed URL and your PAT. The Username is not considered by Artifacts feed and can be any string. Copy the above configuration into a file named 'nuget.config' and run create a Dockerfile like this:

FROM mcr.microsoft.com/dotnet/core/sdk:3.1 AS build

WORKDIR /app

# copy source code, nuget.config file should be placed in the 'src' directory for this to work.
COPY src/ .

# restore nuget packages
RUN dotnet restore --configfile nuget.config

# build
RUN dotnet build

# publish
RUN dotnet publish -o output

# build runtime image
FROM mcr.microsoft.com/dotnet/core/runtime:3.1 AS runtime
WORKDIR /app
COPY --from=build /app/output/ ./
# 
ENTRYPOINT ["your/entry/point"]

This is the simplest thing that will possibly work. But you really shouldn't hard code secrets such as your PAT into your source control system. Very conveniently, the dotnet restore command will do environment variable replacement in the nuget.config file, so you can replace your hard-coded PAT with a reference to an ENV var and then pass that to docker build: In your nuget.config file:
    <packageSourceCredentials>
        <DevOpsArtifactsFeed>
            <add key="Username" value="foo" />
            <add key="ClearTextPassword" value="%NUGET_PAT%" />
        </DevOpsArtifactsFeed>
    </packageSourceCredentials>
In your Dockerfile:
ARG NUGET_PAT
ENV NUGET_PAT=$NUGET_PAT
Your docker build command:
docker build -t my-image --build-arg NUGET_PAT="your PAT" .
I hope this short post saves somebody from the many hours that I wasted on this. I also hope that Microsoft updates their documentation!

1 comment:

sacha said...

Have been bitten by this before, and came up with much crapper solution to this, where I pre-published stuff and then copied the result of the publish to the new layer for container

Anyway great post, have added my own link here to it : https://sachabarbs.wordpress.com/2020/08/04/restoring-from-an-azure-artifacts-nuget-feed-from-inside-a-docker-build/