Restore Nuget Packages inside a Docker Container

Docker

This week we ran into a problem when we tried to restore Nuget packages inside a Docker container from several private Nuget feeds. To restore Nuget packages from a private feed, we used the VSS_NUGET_EXTERNAL_FEED_ENDPOINTS environment variable and passed a Personal Access Token into the Dockerfile. This worked fine with packages from nuget.org and our private Nuget feed.

In this post we want to show how we restored the Nuget packages inside a Docker container and how we use a nuget.config file to solve our problem now.

So far, we used the following code to restore Nuget packages inside the Dockerfile:

1
2
3
4
5
6
7
8
9
ARG PAT
ENV PAT ${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://4tecture.PrivateNugetFeed/index.json\", \"password\":\"${PAT}\"}]}"

COPY ["MyProject.csproj", ""]
RUN dotnet restore -s "https://4tecture.PrivateNugetFeed/index.json" -s "https://api.nuget.org/v3/index.json" "./MyProject.csproj"

This code worked fine when we used only one private feed.

Using multiple Private Nuget Feeds inside the Dockerfile

This week we needed to use a second private Nuget feed to restore Nuget packages inside the Dockerfile. Since we already use the VSS_NUGET_EXTERNAL_FEED_ENDPOINTS environment variable, we passed a JSON list to add another endpoint and provide the same Personal Access Token. Then we added the new private feed to the dotnet restore command. The code looked like this:

1
2
3
4
5
6
7
8
9
ARG PAT
ENV PAT ${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://4tecture.PrivateNugetFeed/index.json\", \"password\":\"${PAT}\"}, {\"endpoint\":\"https://4tecture.AnotherPrivateNugetFeed/index.json\", \"password\":\"${PAT}\"}]}"

COPY ["MyProject.csproj", ""]
RUN dotnet restore -s "https://4tecture.PrivateNugetFeed/index.json" -s "https://api.nuget.org/v3/index.json" -s "https://4tecture.AnotherPrivateNugetFeed/index.json" "./MyProject.csproj"

This approach sounds great in theory but it didn’t work for us. The password wasn’t passed to the second private Nuget feed. Therefore, we had to come up with a different solution. We already use a nuget.config file in our projects and so we decided to use it and add the credentials to the private feeds during the build.

Configure private Nuget Feeds with a nuget.config File

The nuget.config file is in the same folder as your solution and contains the Url to all needed Nuget feeds as XML. Our nuget.config file looked like this:

1
2
3
4
5
6
7
8
9
10
11
12
<?xml version="1.0" encoding="utf-8"?>
<configuration>
  <packageSources>
    <clear />
    <add key="nuget.org" value="https://api.nuget.org/v3/index.json" />
    <add key="Products" value="https://4tecture.PrivateNugetFeed/index.json" />    
    <add key="DemoFeed" value="https://4tecture.AnotherPrivateNugetFeed/index.json" />  
  </packageSources>
  <activePackageSource>
    <add key="All" value="(Aggregate source)" />
  </activePackageSource>
</configuration>

The config file doesn’t contain any passwords or authentication. We want this for two reasons. First, never check-in passwords into source control! Second, we want every developer to be able to use their authentication method.

Edit the nuget.config File in the CI Pipeline

Since the authentication is not in the nuget.config file, we have to add it during the CI pipeline. To change the content of the file, we use stream editor sed inside the Dockerfile. We will replace the last tag of the nuget.config file, </configuration>, with the packageSourceCredentials section. Inside this section, we provide a username and password. For both values, we provide our Personal Access Token. Additionally, we have to copy the nuget.config file inside the build container and provide the file path to the file during the dotnet restore command. The final code of the Dockerfile looks like this:

1
2
3
4
5
6
7
8
9
COPY ["MyProject.csproj", ""]
COPY ["nuget.config", ""]

ARG PAT
RUN sed -i "s|</configuration>|<packageSourceCredentials><Products><add key=\"Username\" value=\"PAT\" /><add key=\"ClearTextPassword\" value=\"${PAT}\" /></Products><DemoFeed><add key=\"Username\" value=\"PAT\" /><add key=\"ClearTextPassword\" value=\"${PAT}\" /></DemoFeed></packageSourceCredentials></configuration>|" nuget.config
RUN dotnet restore --configfile "./nuget.config" "./MyProject.csproj"

COPY . .
RUN dotnet build "MyProject.csproj" -c Release -o /app/build --no-restore

Note: This example used a Linux container to build the solution. If you want to replace text using a Windows container, please find a PowerShell alternative to the Linux sed command.

Conclusion

Restoring Nuget packages from private feeds can be tricky. Use a nuget.config file with the URLs to all Nuget feeds and add the credentials during the build inside of the CI pipeline. If you are interested in more training or best practices about DevOps, Docker or CI/CD pipelines contact us at info@4tecture.ch.

Tags: