Using Azure Artifacts in Docker builds

One of the projects I work on recently transitioned from running on Azure Web Apps to running on Azure AKS. As part of that transition, all the individual components needed to be built into Docker container images. This turned out to be a non-trivial exercise, because we were using private NuGet feeds on Azure Artifacts to host our shared internal tools.

We run our builds on Azure Pipelines, and while it has decent support for Azure Artifacts repositories in particular, it doesn’t really help with builds that occur inside Docker. I’m going to spare you the gory details of all the weird and wonderful workarounds I attempted, including building a NuGet package cache image (with every version of every private package in it!) as a separate task, and go to the solution that actually works.

Include the Azure Artifacts credential provider in your build image

For now, you’ll probably find it easiest to have a custom build image based off of the .NET core SDK image. This is changing with .NET Core 3.0, because the SDK image is slated to include the Azure Artifacts Credential Provider out of the box. Meanwhile, you can do something like this:

FROM microsoft/dotnet:2.2-sdk AS build

# Install locale support
RUN apt-get update \
        && apt-get install -y --no-install-recommends \
        apt-transport-https \
        && apt-get update \
        && apt-get install -y --no-install-recommends locales \
        && rm -rf /var/lib/apt/lists/*

# Enable en_us.UTF-8 in /etc/locale.gen
RUN sed -i 's/# en_US.UTF-8 UTF-8/en_US.UTF-8 UTF-8/' /etc/locale.gen && locale-gen

# Set locale to UTF-8
ENV LC_ALL=en_US.UTF-8

# Install Azure Artifacts credential provider
RUN curl -o https://raw.githubusercontent.com/Microsoft/artifacts-credprovider/master/helpers/installcredprovider.sh
RUN sh installcredprovider.sh

At least for the version we were using, configuring the locale was important: without it, the credential provider fails silently when used.

Build the image above, and tag it accordingly, so you can use it in the final build.

Configure your app image to use the base image and the credential provider

FROM microsoft/dotnet:2.1.8-aspnetcore-runtime AS base
WORKDIR /app
EXPOSE 80

FROM someregistry.example.com/tools/dotnet-build:2.2-sdk AS build
RUN mkdir -p /root/.nuget/NuGet/
COPY ["NuGet.Config", "/root/.nuget/NuGet/NuGet.Config"]

WORKDIR /src
COPY ["App/App.csproj", "App/"]

ARG nuget_external_endpoints
ENV VSS_NUGET_EXTERNAL_FEED_ENDPOINTS "$nuget_external_endpoints"

RUN dotnet restore "App/App.csproj"
COPY . .
WORKDIR "/src/App"
RUN dotnet build "App.csproj" -c Release -o /app

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

FROM base AS final
WORKDIR /app
COPY --from=publish /app .
CMD ["dotnet", "App.dll"]

There are only a few of noteworthy things here:

First, I’m copying nuget.config over, because it defines the feeds we are using. It does not contain any credentials to the feed.

Second, I’m using the previously built image someregistry.example.com/tools/dotnet-build:2.2-sdk as the build environment instead of the regular microsoft/dotnet:2.2-sdk.

Finally, I’m defining a build argument called nuget_external_endpoints and setting the value of that argument to the environment variable VSS_NUGET_EXTERNAL_FEED_ENDPOINTS.

Generate a Personal Access Token in Azure DevOps

In order to keep things secure, we’ll opt to use an access token that only has read access to the Azure Artifacts feed. Navigate to https://<yourtenant>.visualstudio.com/_usersSettings/tokens and create a new token. Name the token, select a suitable lifetime for it, then click on “Show all scopes” at the bottom of the screen. Select the Read permission for Packages, and create the token:

Test the build locally

At this point, it’s a good idea to see if things work in your development environment. Try building your image, by running:

docker build . -f .\Dockerfile --build-arg 'nuget_external_endpoints={\"endpointCredentials\": [{\"endpoint\": \"<your feed endpoint>\", \"password\": \"<your access token>\"}]}'

Note: the quoting above is what works for PowerShell. If you use a different OS or shell, you may need to adjust accordingly.

Configure the build

The final step is to use the PAT as part of the build. You can accomplish that by adding the Docker build task in Pipelines and adding the following snippet as a build argument:

nuget_external_endpoints={"endpointCredentials": [{"endpoint":"<your feed endpoint>", "password":"$(AzureDevOpsAccessToken)"}]}

Quoting the JSON here is not necessary. The build task will pass the argument along intact.

After that, once you add a build variable named AzureDevOpsAccessToken, you’re good to go.

Caveats

Having a custom build image means that when Microsoft releases a new version of the SDK, it’s up to you to update your image to match that. It’s equally possible to set up the Azure Artifacts credential provider inside the actual app Dockerfile, and depending on your circumstances, you may want to do that instead.

Leave a Reply

Your email address will not be published. Required fields are marked *

This site uses Akismet to reduce spam. Learn how your comment data is processed.