Private Feeds

This section documents MSBuild 18.9, the .NET SDK 10.0.402 (with its dotnet CLI) and NuGet 7.9, as published in the MSBuild, .NET and NuGet documentation on learn.microsoft.com, which is the reference these pages are written and verified against. Material about .NET 11 / NuGet 8.0 is still in preview and is always flagged as such — never presented as baseline.

This content was generated with the assistance of AI and should be verified against the official documentation before being relied on in production.

The C# Reference bibliography lists the reference material consulted while preparing these pages.

Not every package belongs on nuget.org: internal libraries, prereleases for a few testers, forks with local patches, or a mirror for builds that must not depend on the internet. NuGet talks to any feed that implements its V3 API, and to plain folders. This page sets up the options Ledgerline could use: a folder feed and a BaGetter server were run here with .NET SDK 10.0.402 (NuGet 7.9.0); GitHub Packages, Azure Artifacts and NuGet.Server are "not run here", with placeholders. Consuming several feeds safely — source mapping and credentials — is on Package Sources and Source Mapping and Authenticated Feeds and Credential Providers.

Folder feeds

A folder (local or a file share) is a feed. dotnet nuget push to a folder path just copies the file; it uses the hierarchical layout (<id>/<version>/<id>.<version>.nupkg, much faster to read) only if the folder already holds at least one package laid out that way. Into an empty folder:

dotnet nuget push artifacts/rel/Ledgerline.1.0.1.nupkg -s ./folderfeed
dotnet nuget push artifacts/rel/Ledgerline.1.0.1.nupkg -s ./folderfeed
Pushing Ledgerline.1.0.1.nupkg to '.../folderfeed'...
Your package was pushed.
Pushing Ledgerline.1.0.1.nupkg to '.../folderfeed'...
Your package was pushed.

The result was a flat folder with Ledgerline.1.0.1.nupkg, and the second push silently overwrote it. Seeded with one package folder copied from the global-packages folder (the documented trick), the next push went hierarchical, with the .nupkg.sha512 and .nuspec beside it, and pushing the same version again failed:

ledgerline.money/1.0.0/.nupkg.metadata
ledgerline.money/1.0.0/ledgerline.money.1.0.0.nupkg
ledgerline.money/1.0.0/ledgerline.money.1.0.0.nupkg.sha512
ledgerline.money/1.0.0/ledgerline.money.nuspec
ledgerline.money/1.0.0/lib/netstandard2.0/_._

error: Package 'Ledgerline.Money.1.0.0' already exists at feed '.../folderfeed-h'.

nuget add <nupkg> -source <folder> (nuget.exe) always writes the hierarchical layout, and nuget init <src> <dest> converts a flat folder of packages into one. Folder feeds have no authentication beyond file permissions, no search index and no vulnerability data — configure auditSources for NuGetAudit.

Self-hosted servers

BaGetter (third-party, open source) is a small NuGet V3 server with a database and file storage; version 1.6.5 ran here in Docker:

docker run -d --name bagetter -p 5555:8080 \
  -e ApiKey="$BAGETTER_API_KEY" \
  -e Storage__Type=FileSystem -e Storage__Path=/data \
  -e Database__Type=Sqlite -e "Database__ConnectionString=Data Source=/data/db/bagetter.db" \
  -e Search__Type=Database \
  bagetter/bagetter:1.6.5

Its service index offers publish, symbol publish, search, autocomplete, registrations and the package base address — enough for push, restore and search, but no vulnerability data (NuGet Server API). Pushes with a wrong key got 401 (Unauthorized), duplicates 409 (Conflict); dotnet nuget delete unlisted a version (Managing Published Packages). Behind HTTP it needs allowInsecureConnections="true" in nuget.config, acceptable only on localhost — anywhere else, put it behind HTTPS (NU1302).

NuGet.Server 3.4.3 (.NET Foundation) is an older option: an ASP.NET (.NET Framework) web application that serves a folder over the V2 (OData) protocol, on IIS (Windows). Push goes to /nuget (and /api/v2/package), with an apiKey in web.config; it restructures the folder into the hierarchical layout on first run. Not run here.

GitHub Packages (not run here)

The NuGet registry of GitHub Packages lives at https://nuget.pkg.github.com/NAMESPACE/index.json, where NAMESPACE is the user or organization. Authentication:

  • In GitHub Actions: GITHUB_TOKEN publishes packages connected to the workflow’s repository, and reads packages the repository was granted access to.

  • Elsewhere: a personal access token (classic) — fine-grained tokens are not supported — with read:packages to install and write:packages to publish.

dotnet nuget add source "https://nuget.pkg.github.com/ledgerline/index.json" --name github \
  --username ledgerline-bot --password "$GITHUB_TOKEN" --store-password-in-clear-text
dotnet nuget push "artifacts/*.nupkg" --source github --api-key "$GITHUB_TOKEN"

(--store-password-in-clear-text because there is no encryption on Linux and macOS — run it on a CI runner, not into a committed file.) A package with a RepositoryUrl pointing at a GitHub repository is connected to that repository and inherits its permissions; several packages can share one repository that way.

A documented pitfall: with GitHub Packages and nuget.org both configured, restores of ordinary public packages (Microsoft.Extensions.*) can fail intermittently with 403, because NuGet asks every source for every package and a GitHub authentication hiccup stops the restore. The fix GitHub gives is package source mapping — only Ledgerline.* goes to github:

<packageSourceMapping>
  <packageSource key="nuget.org"><package pattern="*" /></packageSource>
  <packageSource key="github"><package pattern="Ledgerline.*" /></packageSource>
</packageSourceMapping>

Azure Artifacts (not run here)

An Azure DevOps feed is scoped to a project or the whole organization, private or public, and can include upstream sources (nuget.org and others):

https://pkgs.dev.azure.com/<ORGANIZATION>/<PROJECT>/_packaging/<FEED>/nuget/v3/index.json   (project-scoped)
https://pkgs.dev.azure.com/<ORGANIZATION>/_packaging/<FEED>/nuget/v3/index.json             (organization-scoped)
  • Authentication — the Azure Artifacts Credential Provider (a NuGet plugin) on developer machines; in Azure Pipelines the NuGetAuthenticate@1 task; elsewhere a PAT added with dotnet nuget add source <url> --username <user> --password <PAT>. The push API key is ignored and can be any string (the docs use AZ).

  • Permissions — pushing needs the Feed Publisher (Contributor) role; with less, the push fails with 403. A new feed gives the project’s Build Service Feed and Upstream Reader (Collaborator) only.

  • Immutable versions — a version can’t be overwritten or reused, even after it is deleted.

  • Upstream sources — searched in a fixed order (packages published to the feed, then packages already saved from upstreams, then each upstream in its listed order), unlike NuGet’s parallel queries. A package installed from an upstream by a Collaborator or higher is saved in the feed, so builds survive an outage of nuget.org. A version that exists upstream can’t be published to the feed unless that upstream is disabled first.

  • Views — @Local (the default, where every published package lands), @Prerelease and @Release; promoting a version to a view releases it to the consumers of that view.

dotnet nuget push --source "https://pkgs.dev.azure.com/ledgerline/Ledgerline/_packaging/ledgerline-internal/nuget/v3/index.json" \
  --api-key AZ artifacts/Ledgerline.1.0.1.nupkg

Azure DevOps as a whole is covered in Azure — Developer Tools and DevOps.

Choosing

Feed Good for Watch out for

Folder

Local testing; offline CI caches

No auth, no search, no audit data; flat layout overwrites silently

BaGetter

A small team; rehearsing nuget.org pushes

You run it: backups, HTTPS, upgrades

GitHub Packages

Repositories on GitHub; GITHUB_TOKEN in Actions

Classic PATs only outside Actions; the 403 without source mapping

Azure Artifacts

Azure DevOps organizations; upstream caching; views

Credential provider setup; immutable versions

Common mistakes

  • A private feed without source mapping — dependency confusion and the GitHub 403.

  • Credentials in a committed nuget.config — use environment variables or the credential provider.

  • Pushing to a flat folder twice — the first package is overwritten without a word.

  • allowInsecureConnections on a real network — packages and credentials in clear text.

  • Expecting to "fix" a version on Azure Artifacts by deleting it — the number stays taken.