Skip to content

NuGet Feed Setup

Section: 16 — Packages
Last Updated: 2026-05-30
Scope: Configure the Azure DevOps private NuGet feed for local development and CI/CD


Overview

All Microtec packages are published to a private Azure Artifacts feed hosted in Azure DevOps. Every developer machine and every CI/CD pipeline must authenticate to this feed before running dotnet restore or dotnet build.

Feed URL

https://pkgs.dev.azure.com/microtec/_packaging/Microtec/nuget/v3/index.json


Local Development Setup

Step 1 — Generate a Personal Access Token (PAT)

  1. Open Azure DevOpshttps://dev.azure.com/microtec
  2. Click your profile avatar (top right) → Personal Access Tokens
  3. Click + New Token
FieldValue
Namenuget-local-{your-alias}
Organizationmicrotec
Expiration90 days (maximum recommended)
ScopePackaging → Read
  1. Copy the generated token — it is shown only once.

PAT Expiry

Set a calendar reminder 2 weeks before expiry. An expired PAT causes 401 Unauthorized during dotnet restore with no clear error message beyond the feed URL.

Step 2 — Add the NuGet Source

Run the following command, replacing <YOUR_PAT> with the token you just created:

bash
dotnet nuget add source \
  "https://pkgs.dev.azure.com/microtec/_packaging/Microtec/nuget/v3/index.json" \
  --name "Microtec" \
  --username "any" \
  --password "<YOUR_PAT>" \
  --store-password-in-clear-text

--store-password-in-clear-text

This flag is required on Linux and macOS because the .NET credential store on those platforms does not support encrypted credential storage in the same way Windows does. The PAT is stored in ~/.nuget/NuGet/NuGet.Config.

Step 3 — Verify the Source

bash
dotnet nuget list source

Expected output includes:

  Enabled
  -------
  3. Microtec [Enabled]
     https://pkgs.dev.azure.com/microtec/_packaging/Microtec/nuget/v3/index.json

Step 4 — Test Restore

bash
cd Platforms
dotnet restore Microtec.Platforms.sln

A successful restore shows packages being downloaded from the Microtec source. No errors about missing packages.


Project-Level nuget.config

The Platforms/ directory contains a nuget.config file committed to source control. This file declares the feed so that developers do not need to memorise the URL — they only need to supply credentials.

xml
<!-- Platforms/nuget.config -->
<?xml version="1.0" encoding="utf-8"?>
<configuration>
  <packageSources>
    <clear />
    <add key="nuget.org" value="https://api.nuget.org/v3/index.json" />
    <add key="Microtec"
         value="https://pkgs.dev.azure.com/microtec/_packaging/Microtec/nuget/v3/index.json" />
  </packageSources>
  <packageSourceMapping>
    <packageSource key="nuget.org">
      <package pattern="*" />
    </packageSource>
    <packageSource key="Microtec">
      <package pattern="Microtec.*" />
    </packageSource>
  </packageSourceMapping>
</configuration>

The <packageSourceMapping> block ensures Microtec.* packages are always resolved from the private feed only, and all other packages come from nuget.org. This prevents accidental dependency confusion attacks.


CI/CD Authentication (Azure DevOps Pipelines)

Pipelines use the built-in $(System.AccessToken) — no PAT management required.

Pipeline nuget.config injection

The build template injects credentials at runtime using the NuGetAuthenticate task:

yaml
steps:
  - task: NuGetAuthenticate@1
    displayName: Authenticate NuGet feed

  - script: dotnet restore Microtec.Platforms.sln
    displayName: Restore packages

NuGetAuthenticate@1 automatically populates $(System.AccessToken) as the credential for all sources declared in the project's nuget.config. You do not need to hardcode any tokens in pipeline YAML.

Docker builds — secret mount

Dockerfiles use --mount=type=secret to pass the PAT without baking it into the image:

dockerfile
RUN --mount=type=secret,id=nuget_pat \
    NUGET_PAT=$(cat /run/secrets/nuget_pat) \
    dotnet restore "MyService.Apis/MyService.Apis.csproj" \
    -s "https://pkgs.dev.azure.com/microtec/_packaging/Microtec/nuget/v3/index.json" \
    --configfile /dev/null \
    /p:NuGetFeedUsername=any \
    /p:NuGetFeedPassword=${NUGET_PAT}

The pipeline passes the secret:

yaml
- script: |
    docker build \
      --secret id=nuget_pat,env=SYSTEM_ACCESSTOKEN \
      -t $(imageName) .
  env:
    SYSTEM_ACCESSTOKEN: $(System.AccessToken)

Never bake the PAT into the image

Do not use ARG NUGET_PAT + ENV NUGET_PAT in Dockerfiles — this stores the token in every image layer and in the Docker build cache. Always use --mount=type=secret.


Troubleshooting

SymptomLikely CauseFix
Unable to load the service index for sourceWrong feed URL or no networkVerify the URL matches exactly; check VPN
401 UnauthorizedPAT expired or wrong scopeRegenerate PAT with Packaging → Read scope
Package 'Microtec.X' is not foundFeed not listed or package not publishedRun dotnet nuget list source; verify feed is enabled
Response status code does not indicate success: 403PAT has insufficient scopeEnsure Packaging → Read is ticked, not just Packaging
Build fails in Docker but not locallySecret not mountedVerify --secret id=nuget_pat,env=... in docker build command

Rotating a PAT

  1. Generate a new PAT in Azure DevOps (keep the old one active during transition)
  2. Update your local source:
    bash
    dotnet nuget update source "Microtec" \
      --username "any" \
      --password "<NEW_PAT>" \
      --store-password-in-clear-text
  3. If using pipeline variables, update the NUGET_PAT variable in Azure DevOps Library
  4. Revoke the old PAT

Internal Documentation — Microtec Platform Team