NuGet Feed Authentication in CI - nuget.config Credentials
Private feeds work locally because your machine has cached or interactive credentials, but CI is non-interactive and starts clean. Restore fails with 401/403 until the runner is given a token explicitly - through nuget.config credentials or dotnet nuget add source.
What this error means
Restore that succeeds locally fails in CI on a private feed with a 401 or 403, because the runner has no stored credential. It is deterministic until credentials are wired in.
error : Unable to load the service index for source https://pkgs.dev.azure.com/...
error : Response status code does not indicate success: 401 (Unauthorized).Common causes
No credential exists on the clean runner
CI runners do not carry your interactive feed login; without an injected token, restore against a private feed is rejected.
Credentials were hardcoded or omitted
A nuget.config with no packageSourceCredentials, or one with a stale inline secret, leaves restore unauthenticated.
How to fix it
Add the source with a token at build time
- Add the authenticated source with
dotnet nuget add sourceusing a CI secret. - Use
--store-password-in-clear-textonly on ephemeral CI runners. - Run restore after the source is added.
dotnet nuget add source https://nuget.pkg.github.com/contoso/index.json \
--name github --username contoso-ci \
--password ${{ secrets.NUGET_TOKEN }} --store-password-in-clear-textReference the secret from nuget.config
- Commit a
nuget.configthat points credentials at an env var. - Set the env var from a CI secret in the workflow.
- Restore reads the credential at runtime.
<packageSourceCredentials>
<github>
<add key="Username" value="contoso-ci" />
<add key="ClearTextPassword" value="${NUGET_TOKEN}" />
</github>
</packageSourceCredentials>How to prevent it
- Inject feed tokens from CI secrets, never commit them.
- Keep a committed
nuget.configdescribing every source the build needs. - Use a least-privilege CI identity with only feed read access.