Artifacts

Configuring Upstream Sources for Azure Artifacts

A practical guide to Azure Artifacts upstream sources covering proxy configuration, caching behavior, feed-to-feed upstreams, and supply chain security.

Configuring Upstream Sources for Azure Artifacts

If you have ever managed package dependencies across multiple teams, you know the pain of juggling public registries, private packages, and internal shared libraries. Azure Artifacts upstream sources solve this problem by giving you a single feed endpoint that proxies and caches packages from multiple registries. You point your tooling at one feed, and Azure Artifacts handles the rest.

This guide walks through how upstream sources work, how to configure them for npm, NuGet, and Maven, and how to use feed-to-feed upstreams to build a layered package resolution strategy across your organization.

What Are Upstream Sources?

An upstream source is a connection from your Azure Artifacts feed to another package registry. When a developer requests a package that does not exist in your feed, Azure Artifacts checks the configured upstream sources, retrieves the package, caches it locally in your feed, and serves it back to the developer. From that point forward, the cached copy is served directly from your feed.

Think of it as a transparent proxy with a persistent cache. Your feed becomes the single source of truth for all packages your team needs, whether they are internal packages you publish directly or public packages pulled from npmjs.org, nuget.org, or Maven Central.

The key benefits are straightforward:

  • Single endpoint configuration. Developers configure one registry URL. No juggling multiple .npmrc entries or NuGet sources.
  • Deterministic builds. Once a package version is cached in your feed, it stays there. Even if the upstream registry goes down or a package is unpublished, your cached copy remains available.
  • Supply chain protection. All packages flow through a single controlled channel where you can apply policies, scan for vulnerabilities, and audit consumption.

How Upstream Resolution Works

Understanding the resolution order is critical because it affects which version of a package your developers actually receive.

When a package is requested from your feed, Azure Artifacts follows this sequence:

  1. Feed-local packages first. If the package exists in the feed (either published directly or previously cached from an upstream), it is served immediately.
  2. Upstream sources in priority order. If the package is not found locally, Azure Artifacts checks each upstream source in the order you have configured them, top to bottom.
  3. First match wins. The first upstream source that contains the requested package version wins. The package is fetched, cached in your feed, and returned.

This ordering matters. If you have an internal feed and npmjs.org both configured as upstreams, and both contain a package named utils, the upstream listed first takes priority. This is a deliberate design choice for supply chain security, but it can also cause confusion if you are not aware of it.

Configuring npmjs.org as an Upstream Source

Most teams start here. You want your Azure Artifacts feed to proxy npm packages from the public registry so developers only need to authenticate against your feed.

Step 1: Create or Edit Your Feed

Navigate to your Azure DevOps project, go to Artifacts, and either create a new feed or select an existing one. Click the gear icon to open feed settings, then select Upstream sources.

Click Add upstream source and select Public source. Choose npmjs from the dropdown. The upstream URL is pre-populated as https://registry.npmjs.org.

Step 2: Configure Your .npmrc

Point your npm client at your Azure Artifacts feed instead of the public registry.

# Generate a feed-scoped .npmrc
# Replace {organization}, {project}, and {feed} with your values
npm config set registry https://pkgs.dev.azure.com/{organization}/{project}/_packaging/{feed}/npm/registry/

For authentication, create a .npmrc file in your project root:

registry=https://pkgs.dev.azure.com/myorg/myproject/_packaging/shared-packages/npm/registry/
always-auth=true

; Authenticate to the feed
//pkgs.dev.azure.com/myorg/myproject/_packaging/shared-packages/npm/registry/:username=myorg
//pkgs.dev.azure.com/myorg/myproject/_packaging/shared-packages/npm/registry/:_password=${NPM_TOKEN}
//pkgs.dev.azure.com/myorg/myproject/_packaging/shared-packages/npm/registry/:[email protected]

The NPM_TOKEN value is your personal access token (PAT) encoded as base64. Generate it with:

# On Linux/macOS
echo -n "your-pat-here" | base64

# On Windows (PowerShell)
[Convert]::ToBase64String([Text.Encoding]::UTF8.GetBytes("your-pat-here"))

Step 3: Verify Resolution

Install any public package and confirm it resolves through your feed:

npm install express

After installation, check your Azure Artifacts feed in the portal. You should see express and its transitive dependencies listed as packages with an upstream source indicator showing they originated from npmjs.org.

Configuring nuget.org as an Upstream Source

The process for NuGet is similar. In your feed settings, add a new upstream source and select nuget.org as the public source.

Then configure your NuGet client to use only your feed. Create or update your nuget.config:

<?xml version="1.0" encoding="utf-8"?>
<configuration>
  <packageSources>
    <clear />
    <add key="SharedPackages"
         value="https://pkgs.dev.azure.com/myorg/myproject/_packaging/shared-packages/nuget/v3/index.json" />
  </packageSources>
  <packageSourceCredentials>
    <SharedPackages>
      <add key="Username" value="myorg" />
      <add key="ClearTextPassword" value="%NUGET_PAT%" />
    </SharedPackages>
  </packageSourceCredentials>
</configuration>

The <clear /> directive is important. It removes the default nuget.org source so that all package resolution flows through your feed. Without it, NuGet would bypass your feed for public packages, defeating the purpose of the upstream configuration.

Configuring Maven Central as an Upstream Source

For Java and Kotlin projects, add Maven Central as an upstream. In the feed settings, select Maven Central from the public sources list.

Update your settings.xml to point at your feed:

<settings>
  <servers>
    <server>
      <id>shared-packages</id>
      <username>myorg</username>
      <password>${env.MAVEN_PAT}</password>
    </server>
  </servers>
  <profiles>
    <profile>
      <id>azure-artifacts</id>
      <repositories>
        <repository>
          <id>shared-packages</id>
          <url>https://pkgs.dev.azure.com/myorg/myproject/_packaging/shared-packages/maven/v1</url>
          <releases>
            <enabled>true</enabled>
          </releases>
          <snapshots>
            <enabled>true</enabled>
          </snapshots>
        </repository>
      </repositories>
    </profile>
  </profiles>
  <activeProfiles>
    <activeProfile>azure-artifacts</activeProfile>
  </activeProfiles>
</settings>

Upstream Source Priority and Ordering

The order of your upstream sources defines resolution priority. You can reorder them in the feed settings by dragging entries up or down.

Here is the recommended ordering for most organizations:

  1. Internal shared feed (highest priority) — your organization's shared internal packages.
  2. Team-specific feeds — packages shared within a division or team.
  3. Public registries (lowest priority) — npmjs.org, nuget.org, Maven Central.

This ordering ensures that if your organization publishes a package with the same name as a public package, the internal version takes precedence. This is a deliberate defense against dependency confusion attacks.

Feed-to-Feed Upstreams (Internal Feeds)

This is where upstream sources become genuinely powerful for larger organizations. You can chain feeds together so that a team-specific feed pulls from a shared organizational feed, which in turn pulls from public registries.

Consider this architecture:

team-frontend-feed
  ├── upstream: org-shared-feed
  │     ├── upstream: npmjs.org
  │     └── upstream: internal-components-feed
  └── upstream: npmjs.org (direct fallback)

To set this up, add the shared feed as an upstream source. In your team feed settings, click Add upstream source, select Azure Artifacts feed in this organization, and pick the shared feed.

The feed-to-feed upstream requires that the target feed grants read access to the consuming feed. Navigate to the shared feed's permissions and add the team feed's project collection build service identity (or the specific users) as a Reader.

Permissions for Feed-to-Feed Upstreams

The consuming feed's identity needs at minimum Reader access on the upstream feed. In practice, this means:

  • For project-scoped feeds, the project's build service account needs Reader access on the upstream feed.
  • For organization-scoped feeds, the collection-level build service account needs Reader access.

If permissions are missing, upstream resolution fails silently. Packages simply will not resolve, and the error messages are unhelpful. Always verify permissions first when debugging feed-to-feed resolution issues.

Caching Behavior

When a package is fetched from an upstream source for the first time, Azure Artifacts saves a copy in your feed. This cache has several important properties:

  • Immutable once cached. A specific package version, once cached, does not get updated even if the upstream publishes a change to the same version (which is rare but possible with some registries).
  • Persists indefinitely. Cached packages remain in your feed until you manually delete them or retention policies remove them.
  • Survives upstream outages. If npmjs.org goes down, any package version you have previously resolved continues to work.
  • Counts against storage. Cached upstream packages consume your Azure Artifacts storage quota. For organizations with many projects, this can add up.

You cannot selectively refresh a cached package. If you need a different copy of the same version (extremely rare), you must delete the cached package from your feed and let it re-fetch from the upstream.

Supply Chain Security Benefits

Upstream sources provide several layers of supply chain protection that direct registry access does not.

Dependency confusion prevention. By ordering internal feeds above public registries, you ensure that internal package names always take priority. An attacker cannot publish a malicious package with the same name to npmjs.org and have it silently replace your internal package.

Audit trail. Every package that flows through your feed is logged. You can see exactly which packages your teams consume, when they were first requested, and from which upstream they originated.

Vulnerability scanning. Azure Artifacts integrates with Azure DevOps security features to flag packages with known vulnerabilities. When all packages flow through a single feed, scanning coverage is complete.

Package approval workflows. You can disable upstream sources temporarily or permanently to lock down a feed. Once disabled, only packages already cached or directly published are available. This is useful for hardened production build environments.

Complete Working Example

Let us set up a realistic multi-layer feed configuration for a frontend development team.

Architecture

  • org-shared-feed: Organization-wide feed with upstream to npmjs.org. Internal shared libraries are published here.
  • team-platform-feed: Platform team's feed. Upstreams to org-shared-feed.
  • team-frontend-feed: Frontend team's feed. Upstreams to both org-shared-feed and team-platform-feed.

Step 1: Create the Feeds

Use the Azure CLI with the DevOps extension:

# Install the Azure DevOps extension if not already installed
az extension add --name azure-devops

# Set defaults
az devops configure --defaults organization=https://dev.azure.com/myorg project=myproject

# Create the organization shared feed
az artifacts feed create --name org-shared-feed --description "Organization shared packages"

# Create the platform team feed
az artifacts feed create --name team-platform-feed --description "Platform team packages"

# Create the frontend team feed
az artifacts feed create --name team-frontend-feed --description "Frontend team packages"

Step 2: Configure Upstream Sources

Currently, upstream source management requires the REST API or the portal UI. Here is the REST API approach:

# Add npmjs upstream to org-shared-feed
curl -X POST \
  "https://feeds.dev.azure.com/myorg/myproject/_apis/packaging/feeds/org-shared-feed/upstreamsources?api-version=7.1" \
  -H "Content-Type: application/json" \
  -H "Authorization: Basic $(echo -n ":${PAT}" | base64)" \
  -d '{
    "name": "npmjs",
    "protocol": "npm",
    "location": "https://registry.npmjs.org/",
    "upstreamSourceType": "public"
  }'

# Add nuget.org upstream to org-shared-feed
curl -X POST \
  "https://feeds.dev.azure.com/myorg/myproject/_apis/packaging/feeds/org-shared-feed/upstreamsources?api-version=7.1" \
  -H "Content-Type: application/json" \
  -H "Authorization: Basic $(echo -n ":${PAT}" | base64)" \
  -d '{
    "name": "nuget.org",
    "protocol": "nuget",
    "location": "https://api.nuget.org/v3/index.json",
    "upstreamSourceType": "public"
  }'

# Add org-shared-feed as upstream to team-frontend-feed
curl -X POST \
  "https://feeds.dev.azure.com/myorg/myproject/_apis/packaging/feeds/team-frontend-feed/upstreamsources?api-version=7.1" \
  -H "Content-Type: application/json" \
  -H "Authorization: Basic $(echo -n ":${PAT}" | base64)" \
  -d '{
    "name": "org-shared-feed",
    "protocol": "npm",
    "location": "https://pkgs.dev.azure.com/myorg/myproject/_packaging/org-shared-feed/npm/registry/",
    "upstreamSourceType": "internal"
  }'

Step 3: Pipeline Configuration

Configure your Azure Pipeline to authenticate against the team feed and resolve packages through the upstream chain:

trigger:
  branches:
    include:
      - main

pool:
  vmImage: "ubuntu-latest"

steps:
  - task: NodeTool@0
    inputs:
      versionSpec: "20.x"
    displayName: "Install Node.js"

  - task: npmAuthenticate@0
    inputs:
      workingFile: ".npmrc"
    displayName: "Authenticate npm feed"

  - script: |
      npm ci
    displayName: "Install dependencies"

  - script: |
      npm run build
      npm test
    displayName: "Build and test"

  - task: Npm@1
    inputs:
      command: "publish"
      workingDir: "$(System.DefaultWorkingDirectory)"
      publishRegistry: "useFeed"
      publishFeed: "myproject/team-frontend-feed"
    displayName: "Publish to team feed"
    condition: and(succeeded(), eq(variables['Build.SourceBranch'], 'refs/heads/main'))

The corresponding .npmrc for the pipeline:

registry=https://pkgs.dev.azure.com/myorg/myproject/_packaging/team-frontend-feed/npm/registry/
always-auth=true

Step 4: Verify the Upstream Chain

After running npm install, verify packages resolved correctly:

# Check which packages are cached in your feed
az artifacts package list --feed team-frontend-feed --output table

# Verify a specific package shows the upstream source
az artifacts package show --feed team-frontend-feed --package-name express --output json

In the Azure DevOps portal, navigate to your feed and inspect the package list. Packages resolved from upstream sources display a small indicator showing their origin (e.g., "npmjs" or "org-shared-feed").

Handling Upstream Outages

When an upstream source is unavailable, any package version already cached in your feed continues to work. New package versions that have not been cached will fail to resolve.

To protect against upstream outages:

  1. Run CI builds regularly. Each successful build caches any new dependency versions in your feed.
  2. Use lockfiles. package-lock.json, packages.lock.json, and pom.xml with pinned versions ensure you only request versions that are likely already cached.
  3. Monitor upstream health. Azure Artifacts does not proactively notify you when an upstream is unreachable. Build failures are your first signal.

Upstream Source Limitations

There are constraints you should be aware of:

  • No selective caching. You cannot configure rules like "only cache packages matching @myorg/* scope." All resolved packages are cached.
  • No version filtering. You cannot restrict which versions are available through an upstream. If it exists upstream, it is available.
  • Storage consumption. Every cached package counts against your Artifacts storage. Large dependency trees (looking at you, npm) can consume gigabytes quickly.
  • Protocol-specific upstreams. Each upstream source serves a single protocol. You need separate upstreams for npm, NuGet, and Maven even if they point at the same feed.
  • No push-through publishing. You cannot publish a package to your feed and have it automatically pushed to an upstream. Upstreams are read-only by design.
  • Feed-to-feed depth. While you can chain feeds, deep chains (more than two or three levels) introduce latency and make debugging resolution failures painful.

Comparing Upstream Sources to .npmrc Registry Fallbacks

A common question is why not just list multiple registries in .npmrc and let npm handle the fallback. Here is why upstream sources are better in a team setting.

With multiple .npmrc registries:

# This approach has problems
@myorg:registry=https://pkgs.dev.azure.com/myorg/_packaging/internal/npm/registry/
registry=https://registry.npmjs.org/

This configuration sends scoped package requests to your internal feed and everything else to npmjs.org directly. The issues:

  • No caching. Public packages are fetched from npmjs.org every time. If npmjs goes down, your builds break.
  • No audit trail. You have no visibility into which public packages your teams consume.
  • Split authentication. Developers need credentials for both registries.
  • Dependency confusion risk. Non-scoped internal packages could be shadowed by public packages with the same name.

With upstream sources, all of these problems disappear. One registry endpoint, one authentication flow, complete visibility, and deterministic resolution.

Common Issues and Troubleshooting

1. Packages Not Resolving from Upstream

Symptom: npm install fails with 404 errors for packages that exist on npmjs.org.

Cause: The upstream source is misconfigured, disabled, or the feed's service identity lacks permissions on the upstream feed (for feed-to-feed scenarios).

Fix: Verify the upstream source is enabled in feed settings. For internal upstreams, confirm the consuming feed's identity has Reader access. Check the upstream source URL is correct and the protocol matches.

2. Stale Package Versions

Symptom: A newer version of a package exists on npmjs.org but does not appear in your feed.

Cause: Azure Artifacts caches package metadata with a time-to-live. New versions from upstream sources may take several minutes (up to 3-6 hours in some cases) to appear.

Fix: Wait for the metadata cache to refresh, or delete the cached package entry from your feed to force a fresh lookup on the next request.

3. Storage Quota Exceeded

Symptom: Publishing or caching fails with storage limit errors.

Cause: Cached upstream packages count against your Azure Artifacts storage quota (2 GB for free tier).

Fix: Delete unused cached packages. Set up retention policies to automatically clean old versions. Consider upgrading your Azure DevOps plan for additional storage.

4. Authentication Failures in Pipelines

Symptom: npmAuthenticate task succeeds, but npm install returns 401 errors.

Cause: The .npmrc file references a different feed URL than what the npmAuthenticate task is configured for. The registry URL in .npmrc must exactly match the feed URL, including trailing slashes.

Fix: Ensure the .npmrc registry URL matches exactly. Use the "Connect to feed" button in the Azure Artifacts UI to get the correct URL. Verify that the build service identity has Reader access to the feed.

5. Feed-to-Feed Upstream Returns Empty Results

Symptom: Packages published to an upstream internal feed do not appear in the downstream consuming feed.

Cause: The upstream feed is project-scoped and the downstream feed is in a different project. Cross-project feed visibility requires explicit permission grants.

Fix: In the upstream feed's permissions, add the downstream project's build service account as a Reader. For organization-scoped feeds, use the collection build service identity.

Best Practices

  1. Use a single feed per team with upstream chaining. Resist the urge to create per-project feeds. One feed per team with upstreams to a shared organizational feed strikes the right balance between isolation and reuse.

  2. Always place internal feeds above public registries in upstream ordering. This prevents dependency confusion attacks and ensures your internal packages take precedence.

  3. Use <clear /> in nuget.config and a single registry in .npmrc. Eliminate direct connections to public registries. All package resolution should flow through your Azure Artifacts feed.

  4. Enable retention policies on cached upstream packages. Without retention policies, your feed accumulates every transitive dependency version your team has ever used. Set a policy to retain only the latest N versions of cached packages.

  5. Lock your feeds before production releases. For critical release builds, consider temporarily disabling upstream sources to ensure builds only use vetted, cached packages. This prevents a supply chain attack from affecting a release at the worst possible time.

  6. Monitor feed storage consumption. Set up alerts for storage usage approaching your quota. npm dependency trees are notoriously deep, and cached packages accumulate faster than you expect.

  7. Document your feed topology. Draw out which feeds upstream to which, and share this with your team. When dependency resolution fails, understanding the chain is the first step to debugging it.

  8. Use feed views for release management. Combine upstream sources with feed views (Release, Prerelease, Local) to control which package versions are available to production builds. Promote packages through views as they pass quality gates.

References

Powered by Contentful