Why Package Management Matters
In Modules 4–8, you built pipelines that compile, test, and deploy applications. But here's a question those pipelines don't answer: how do teams share reusable code without copy-pasting it between repositories? How does your authentication library, shared UI components, or internal SDK reach other teams reliably?
The answer is packages — versioned, immutable bundles of code that can be published once and consumed by any team, any project, any pipeline. Azure Artifacts is your private package registry that stores, versions, and distributes these packages.
The Copy-Paste Problem
Without package management, teams resort to dangerous patterns:
| Anti-Pattern | Problem | Package Solution |
|---|---|---|
| Copy-paste code | Bug fixes never propagate, versions drift | Publish a package, consumers update via version bumps |
| Git submodules | Complex, fragile, poor versioning | Package versions are explicit and atomic |
| Shared folder / file share | No versioning, no authentication, no audit trail | Feeds provide auth, versioning, and download history |
| Public registry only | Can't publish proprietary code to npmjs.org | Private feeds for internal packages, upstream for public |
Private Packages vs Public Registries
Most organizations need both:
- Public packages — Open-source libraries from npmjs.org, nuget.org, PyPI, Maven Central
- Private packages — Internal libraries, shared SDKs, company-specific utilities
Azure Artifacts unifies both through upstream sources — a single feed that serves your private packages AND proxies/caches public ones. Developers configure one feed URL and get everything.
For artifact management theory and best practices, see Part 13: Artifact Management.
Feeds — Creating & Configuring
A feed is a container that stores packages. Think of it as a private registry — like having your own npmjs.org or nuget.org, but controlled by your organization.
Organization-Scoped vs Project-Scoped Feeds
| Feed Scope | Visibility | Best For |
|---|---|---|
| Organization-scoped | All projects in the organization can access | Shared libraries used across multiple teams/projects |
| Project-scoped | Only the specific project can access | Project-specific packages, isolation between teams |
Recommendation: Start with organization-scoped feeds for shared libraries. Use project-scoped feeds only when you need strict isolation (e.g., different business units that shouldn't see each other's packages).
Creating Feeds via the Web UI
- Navigate to Artifacts in the left sidebar
- Click "+ Create Feed"
- Name your feed (e.g.,
internal-packages) - Choose scope: Organization or Project
- Choose visibility: Members of the organization, or specific project members
- Check "Include packages from common public sources" to enable upstream sources
Creating Feeds via Azure CLI
# Install the Azure DevOps extension for Azure CLI
az extension add --name azure-devops
# Authenticate to your organization
az devops configure --defaults organization=https://dev.azure.com/yourorg
# Create an organization-scoped feed
az artifacts feed create \
--name "internal-packages" \
--description "Shared libraries for all teams" \
--include-upstream-sources
# Create a project-scoped feed
az artifacts feed create \
--name "team-alpha-packages" \
--project "TeamAlpha" \
--description "Packages specific to Team Alpha"
# List all feeds in the organization
az artifacts feed list --output table
# Show feed details including upstream sources
az artifacts feed show --name "internal-packages"
Feed Permissions
Feeds have role-based access control:
| Role | Can Do | Typical User |
|---|---|---|
| Reader | Download/consume packages | Developers consuming shared libraries |
| Collaborator | Read + save packages from upstream | Developers who trigger upstream caching |
| Contributor | Read + publish new packages | CI/CD pipelines, library maintainers |
| Owner | All above + manage feed settings, permissions | Platform/DevOps engineers |
NuGet Packages (.NET)
NuGet is the package manager for .NET. If your organization builds .NET libraries (class libraries, shared models, SDK wrappers), NuGet packages are how you distribute them.
Pipeline to Pack & Publish NuGet
# Pipeline: Build, pack, and publish a NuGet package to Azure Artifacts
# Triggers on main branch pushes to the shared library project
trigger:
branches:
include: [main]
paths:
include: ['src/SharedLibrary/**']
pool:
vmImage: 'ubuntu-latest'
variables:
buildConfiguration: 'Release'
# Semantic version from .csproj or calculated
majorMinorPatch: '2.1.0'
# Append build number for pre-release versions
packageVersion: '$(majorMinorPatch)-ci.$(Build.BuildId)'
steps:
# Restore dependencies
- task: DotNetCoreCLI@2
displayName: 'Restore NuGet packages'
inputs:
command: 'restore'
projects: 'src/SharedLibrary/**/*.csproj'
# Build the library
- task: DotNetCoreCLI@2
displayName: 'Build library'
inputs:
command: 'build'
projects: 'src/SharedLibrary/**/*.csproj'
arguments: '--configuration $(buildConfiguration) --no-restore'
# Run unit tests
- task: DotNetCoreCLI@2
displayName: 'Run tests'
inputs:
command: 'test'
projects: 'tests/**/*.csproj'
arguments: '--configuration $(buildConfiguration) --no-build'
# Pack into a .nupkg file
- task: DotNetCoreCLI@2
displayName: 'Pack NuGet package'
inputs:
command: 'pack'
packagesToPack: 'src/SharedLibrary/**/*.csproj'
configuration: '$(buildConfiguration)'
versioningScheme: 'byEnvVar'
versionEnvVar: 'packageVersion'
outputDir: '$(Build.ArtifactStagingDirectory)/nuget'
# Publish to Azure Artifacts feed
- task: NuGetCommand@2
displayName: 'Push to Azure Artifacts'
inputs:
command: 'push'
packagesToPush: '$(Build.ArtifactStagingDirectory)/nuget/*.nupkg'
nuGetFeedType: 'internal'
publishVstsFeed: 'internal-packages' # Your feed name
Consuming NuGet Packages — nuget.config
To consume packages from your private feed, add a nuget.config file to the root of your consuming project:
<?xml version="1.0" encoding="utf-8"?>
<configuration>
<packageSources>
<!-- Clear default sources to avoid confusion -->
<clear />
<!-- Your Azure Artifacts feed (serves both private + upstream public) -->
<add key="InternalFeed"
value="https://pkgs.dev.azure.com/yourorg/_packaging/internal-packages/nuget/v3/index.json" />
</packageSources>
<packageSourceCredentials>
<!-- Credentials managed by Azure Artifacts Credential Provider -->
<!-- In pipelines, NuGetAuthenticate task handles this automatically -->
<InternalFeed>
<add key="Username" value="anything" />
<add key="ClearTextPassword" value="%ARTIFACTS_PAT%" />
</InternalFeed>
</packageSourceCredentials>
</configuration>
In pipelines, you don't need credentials in the file — the NuGetAuthenticate@1 task injects credentials automatically:
# In your consuming project's pipeline
steps:
- task: NuGetAuthenticate@1
displayName: 'Authenticate to Azure Artifacts'
- task: DotNetCoreCLI@2
displayName: 'Restore (uses authenticated feed)'
inputs:
command: 'restore'
projects: '**/*.csproj'
feedsToUse: 'config'
nugetConfigPath: 'nuget.config'
npm Packages (Node.js)
npm is the package manager for Node.js and front-end JavaScript. Publishing internal npm packages lets teams share UI components, utility libraries, and API clients.
Pipeline to Publish npm Package
# Pipeline: Build and publish an npm package to Azure Artifacts
# The package is scoped: @yourorg/shared-utils
trigger:
branches:
include: [main]
paths:
include: ['packages/shared-utils/**']
pool:
vmImage: 'ubuntu-latest'
steps:
# Authenticate npm to your Azure Artifacts feed
- task: npmAuthenticate@0
displayName: 'Authenticate npm'
inputs:
workingFile: 'packages/shared-utils/.npmrc'
# Install dependencies
- script: |
cd packages/shared-utils
npm ci
displayName: 'Install dependencies'
# Run tests
- script: |
cd packages/shared-utils
npm test
displayName: 'Run tests'
# Build (if TypeScript or bundled)
- script: |
cd packages/shared-utils
npm run build
displayName: 'Build package'
# Publish to Azure Artifacts
- script: |
cd packages/shared-utils
npm publish
displayName: 'Publish to feed'
.npmrc Configuration
Place a .npmrc file in your project root to point npm at your Azure Artifacts feed:
# .npmrc — Configure npm to use Azure Artifacts feed
# Replace 'yourorg' and 'internal-packages' with your values
# Registry for your scoped packages (@yourorg/*)
@yourorg:registry=https://pkgs.dev.azure.com/yourorg/_packaging/internal-packages/npm/registry/
# Authentication — in pipelines, npmAuthenticate fills this automatically
# For local development, run: npx vsts-npm-auth -config .npmrc
//pkgs.dev.azure.com/yourorg/_packaging/internal-packages/npm/registry/:username=anything
//pkgs.dev.azure.com/yourorg/_packaging/internal-packages/npm/registry/:_password=${ARTIFACTS_TOKEN}
//pkgs.dev.azure.com/yourorg/_packaging/internal-packages/npm/registry/:email=not-required@example.com
//pkgs.dev.azure.com/yourorg/_packaging/internal-packages/npm/:username=anything
//pkgs.dev.azure.com/yourorg/_packaging/internal-packages/npm/:_password=${ARTIFACTS_TOKEN}
//pkgs.dev.azure.com/yourorg/_packaging/internal-packages/npm/:email=not-required@example.com
Local development authentication: Developers run npx vsts-npm-auth -config .npmrc once to set up credentials interactively. The token is stored in the user-level .npmrc (not committed to source control).
Maven Packages (Java)
Maven is the standard build tool and package manager for Java. Azure Artifacts supports Maven repositories for publishing JARs, WARs, and other Java artifacts.
Pipeline for Maven Publish
# Pipeline: Build and publish a Maven artifact to Azure Artifacts
# Publishes to the 'internal-packages' feed
trigger:
branches:
include: [main]
paths:
include: ['libs/java-common/**']
pool:
vmImage: 'ubuntu-latest'
steps:
# Authenticate Maven to Azure Artifacts
- task: MavenAuthenticate@0
displayName: 'Authenticate Maven'
inputs:
artifactsFeeds: 'internal-packages'
# Build, test, and publish
- task: Maven@4
displayName: 'Maven build and deploy'
inputs:
mavenPomFile: 'libs/java-common/pom.xml'
goals: 'deploy'
options: '-DskipTests=false'
publishJUnitResults: true
testResultsFiles: '**/TEST-*.xml'
pom.xml Distribution Management
Add the distribution management section to your pom.xml to tell Maven where to publish:
<!-- pom.xml — Add to your library's pom.xml -->
<distributionManagement>
<repository>
<id>internal-packages</id>
<url>https://pkgs.dev.azure.com/yourorg/_packaging/internal-packages/maven/v1</url>
</repository>
<snapshotRepository>
<id>internal-packages</id>
<url>https://pkgs.dev.azure.com/yourorg/_packaging/internal-packages/maven/v1</url>
</snapshotRepository>
</distributionManagement>
Python Packages
Azure Artifacts supports Python packages via the PyPI-compatible interface. This lets you publish internal Python libraries and install them with pip just like any public package.
Pipeline for Python Package Publish
# Pipeline: Build and publish a Python package to Azure Artifacts
# Uses twine for uploading wheel/sdist distributions
trigger:
branches:
include: [main]
paths:
include: ['libs/py-common/**']
pool:
vmImage: 'ubuntu-latest'
steps:
- task: UsePythonVersion@0
displayName: 'Use Python 3.11'
inputs:
versionSpec: '3.11'
# Install build dependencies
- script: |
python -m pip install --upgrade pip
pip install build twine
displayName: 'Install build tools'
# Run tests
- script: |
cd libs/py-common
pip install -e ".[dev]"
pytest tests/ --junitxml=results.xml
displayName: 'Run tests'
- task: PublishTestResults@2
inputs:
testResultsFiles: 'libs/py-common/results.xml'
testRunTitle: 'Python package tests'
# Build wheel and sdist
- script: |
cd libs/py-common
python -m build
displayName: 'Build distributions'
# Publish to Azure Artifacts using twine
- task: TwineAuthenticate@1
displayName: 'Authenticate twine'
inputs:
artifactFeed: 'internal-packages'
- script: |
cd libs/py-common
twine upload -r internal-packages --config-file $(PYPIRC_PATH) dist/*
displayName: 'Upload to Azure Artifacts'
Consuming Python Packages — pip.conf
# pip.conf (Linux/macOS: ~/.config/pip/pip.conf)
# pip.ini (Windows: %APPDATA%\pip\pip.ini)
# Points pip to your Azure Artifacts feed as an extra index
[global]
extra-index-url = https://pkgs.dev.azure.com/yourorg/_packaging/internal-packages/pypi/simple/
# In pipelines, use the PipAuthenticate task instead:
# - task: PipAuthenticate@1
# inputs:
# artifactFeeds: 'internal-packages'
Universal Packages
Universal Packages are Azure Artifacts' answer to "what if my artifact isn't a NuGet/npm/Maven/Python package?" They can store any file or folder of any size — making them perfect for artifacts that don't fit traditional package manager formats.
Use Cases for Universal Packages
- Machine learning models — Trained model files (500 MB+) versioned alongside code
- Game assets — Textures, audio, level data distributed to build agents
- Firmware binaries — Compiled firmware for embedded devices
- Large test datasets — Test fixtures too large for Git
- Compiled documentation — Generated docs distributed as versioned bundles
- Desktop installers — MSI/DMG/DEB files for internal distribution
Publishing Universal Packages
# Pipeline: Publish a trained ML model as a Universal Package
# Universal Packages handle large files efficiently (chunked upload)
trigger:
branches:
include: [main]
paths:
include: ['models/**']
pool:
vmImage: 'ubuntu-latest'
steps:
# Train or prepare the model (simplified example)
- script: |
mkdir -p $(Build.ArtifactStagingDirectory)/model
cp models/trained-model.onnx $(Build.ArtifactStagingDirectory)/model/
cp models/config.json $(Build.ArtifactStagingDirectory)/model/
cp models/tokenizer.json $(Build.ArtifactStagingDirectory)/model/
displayName: 'Prepare model files'
# Publish as Universal Package
- task: UniversalPackages@0
displayName: 'Publish ML model as Universal Package'
inputs:
command: 'publish'
publishDirectory: '$(Build.ArtifactStagingDirectory)/model'
feedsToUsePublish: 'internal'
vstsFeedPublish: 'internal-packages'
vstsFeedPackagePublish: 'sentiment-model'
versionOption: 'custom'
versionPublish: '3.2.$(Build.BuildId)'
packagePublishDescription: 'Sentiment analysis ONNX model v3.2'
Downloading Universal Packages
# In a consuming pipeline — download the model for deployment
steps:
- task: UniversalPackages@0
displayName: 'Download ML model'
inputs:
command: 'download'
downloadDirectory: '$(Build.SourcesDirectory)/artifacts/model'
feedsToUse: 'internal'
vstsFeed: 'internal-packages'
vstsFeedPackage: 'sentiment-model'
vstsPackageVersion: '3.2.*' # Latest patch in 3.2.x
# CLI: Download a Universal Package locally
az artifacts universal download \
--organization https://dev.azure.com/yourorg \
--feed internal-packages \
--name sentiment-model \
--version 3.2.1 \
--path ./local-model-dir
Upstream Sources
Upstream sources are one of Azure Artifacts' most powerful features — they let your feed act as a proxy and cache for public registries. Developers configure a single feed URL and get both your private packages AND public packages from npmjs.org, nuget.org, PyPI, or Maven Central.
How Upstream Sources Work
flowchart TD
A[Developer requests package X] --> B{Is X in your feed?}
B -->|Yes| C[Serve from feed cache ⚡]
B -->|No| D{Is X in upstream source?}
D -->|Yes| E[Fetch from public registry]
E --> F[Cache in your feed]
F --> C
D -->|No| G[Package not found ]
Why Upstream Sources Matter
Additional benefits:
- Single source of truth — One feed URL in config files, not multiple registry URLs
- Security scanning — All packages flow through one point where you can audit them
- Network efficiency — Popular packages are cached, reducing external bandwidth
- Compliance — You control which public packages are allowed (via upstream filtering)
Configuring Upstream Sources
When creating a feed, check "Include packages from common public sources" to automatically add:
| Package Type | Default Upstream | What It Proxies |
|---|---|---|
| NuGet | nuget.org | All public .NET packages |
| npm | npmjs.org | All public Node.js packages |
| Python | PyPI (pypi.org) | All public Python packages |
| Maven | Maven Central | All public Java packages |
You can also add other Azure Artifacts feeds as upstream sources — creating a hierarchy where a team feed inherits packages from an organization-wide feed.
Versioning Strategies
Package versioning is how consumers know what changed and whether an update is safe. Azure Artifacts enforces immutability — once you publish version 1.2.3, that version can never be overwritten. This guarantees reproducible builds.
Semantic Versioning (SemVer)
The industry standard is Semantic Versioning (major.minor.patch):
| Component | Increment When | Example |
|---|---|---|
| Major (X.0.0) | Breaking changes — consumers must update their code | 2.0.0 → Removed deprecated API |
| Minor (0.X.0) | New features — backward compatible | 2.1.0 → Added new helper method |
| Patch (0.0.X) | Bug fixes — no API changes | 2.1.1 → Fixed null reference bug |
Pre-Release Versions
Pre-release suffixes indicate packages that aren't production-ready:
1.0.0-alpha.1— Early development, API may change drastically1.0.0-beta.1— Feature-complete but testing in progress1.0.0-rc.1— Release candidate, final testing before stable1.0.0-ci.12345— CI build, not for manual consumption
Views: @Release, @Prerelease, @Local
Azure Artifacts views let you categorize packages by quality level. Consumers can subscribe to a specific view to control what they receive:
| View | Contains | Who Uses It |
|---|---|---|
| @Local | All published versions (default) | CI/CD pipelines, library developers |
| @Prerelease | Versions promoted for testing | QA teams, integration testing |
| @Release | Production-quality versions only | Production applications |
Version Management in Pipelines
# Versioning strategy: CI builds get pre-release suffix
# Release builds use clean semantic version
variables:
majorMinor: '3.1'
# For CI builds: 3.1.0-ci.BuildId
# For release builds: 3.1.PatchNumber
${{ if eq(variables['Build.SourceBranchName'], 'main') }}:
packageVersion: '$(majorMinor).$(Build.BuildId)'
isRelease: true
${{ else }}:
packageVersion: '$(majorMinor).0-ci.$(Build.BuildId)'
isRelease: false
steps:
- script: echo "Publishing version $(packageVersion)"
displayName: 'Display version'
# ... build and pack steps ...
# Promote to Release view after publishing (only for main branch)
- task: PowerShell@2
displayName: 'Promote to Release view'
condition: eq(variables.isRelease, true)
inputs:
targetType: 'inline'
script: |
# Promote package to @Release view using REST API
$org = "https://pkgs.dev.azure.com/yourorg"
$feed = "internal-packages"
$pkg = "MyLibrary"
$version = "$(packageVersion)"
$body = @{
views = @{ op = "add"; path = "/views/-"; value = "Release" }
} | ConvertTo-Json -Depth 5
$uri = "$org/_apis/packaging/feeds/$feed/nuget/packages/$pkg/versions/$version?api-version=7.1-preview.1"
Invoke-RestMethod -Uri $uri -Method Patch -Body $body `
-ContentType "application/json" `
-Headers @{ Authorization = "Bearer $(System.AccessToken)" }
Retention Policies
Without retention policies, feeds grow indefinitely — every CI build publishes a new pre-release version, and after a year you have thousands of packages nobody will ever use. Retention policies automatically clean up old versions to manage storage costs.
Storage Costs
| Tier | Storage | Cost |
|---|---|---|
| Free tier | 2 GB per organization | $0 |
| Additional storage | Per GB per month | ~$2/GB/month |
Configuring Retention
Navigate to Feed Settings → Retention policies:
- Maximum number of versions per package — Keep only the last N versions (e.g., 25). Older versions are automatically deleted.
- Days to keep recently downloaded packages — Packages downloaded in the last N days are protected from cleanup regardless of version count.
Protecting Specific Versions
Retention policies won't delete packages that are:
- Promoted to a view — Packages in @Release or @Prerelease are never auto-deleted
- Recently downloaded — Active packages are protected by the "days since last download" rule
- Pinned — Manually pinned versions are exempt from cleanup
Pipeline Integration
The real power of Azure Artifacts comes when packages are published and consumed automatically by pipelines — no manual steps, no credential juggling, no "it works on my machine" package versions.
Authentication Tasks
Each package type has a dedicated authentication task that injects credentials for the current pipeline run:
| Package Type | Auth Task | What It Does |
|---|---|---|
| NuGet | NuGetAuthenticate@1 |
Injects credentials into NuGet credential provider |
| npm | npmAuthenticate@0 |
Writes auth tokens into .npmrc |
| Maven | MavenAuthenticate@0 |
Injects credentials into Maven settings.xml |
| Python (pip) | PipAuthenticate@1 |
Sets PIP_EXTRA_INDEX_URL with credentials |
| Python (twine) | TwineAuthenticate@1 |
Generates .pypirc with upload credentials |
Complete Pipeline: Publish & Consume
# Complete multi-stage pipeline demonstrating:
# 1. Build & publish a shared library to Azure Artifacts
# 2. Consume that library in a downstream application build
trigger:
branches:
include: [main]
stages:
# ─── STAGE 1: PUBLISH LIBRARY ───────────────────────────
- stage: PublishLibrary
displayName: 'Publish Shared Library'
jobs:
- job: PackAndPublish
pool:
vmImage: 'ubuntu-latest'
steps:
- task: DotNetCoreCLI@2
displayName: 'Build library'
inputs:
command: 'build'
projects: 'src/SharedAuth/*.csproj'
arguments: '--configuration Release'
- task: DotNetCoreCLI@2
displayName: 'Pack NuGet'
inputs:
command: 'pack'
packagesToPack: 'src/SharedAuth/*.csproj'
configuration: 'Release'
versioningScheme: 'byBuildNumber'
outputDir: '$(Build.ArtifactStagingDirectory)'
- task: NuGetCommand@2
displayName: 'Push to feed'
inputs:
command: 'push'
packagesToPush: '$(Build.ArtifactStagingDirectory)/*.nupkg'
nuGetFeedType: 'internal'
publishVstsFeed: 'internal-packages'
# ─── STAGE 2: CONSUME IN APPLICATION ────────────────────
- stage: BuildApp
displayName: 'Build Application (Consumes Library)'
dependsOn: PublishLibrary
jobs:
- job: BuildWebApp
pool:
vmImage: 'ubuntu-latest'
steps:
# Authenticate to feed for package restore
- task: NuGetAuthenticate@1
displayName: 'Auth to Azure Artifacts'
- task: DotNetCoreCLI@2
displayName: 'Restore (includes private package)'
inputs:
command: 'restore'
projects: 'src/WebApp/*.csproj'
feedsToUse: 'config'
nugetConfigPath: 'nuget.config'
- task: DotNetCoreCLI@2
displayName: 'Build application'
inputs:
command: 'build'
projects: 'src/WebApp/*.csproj'
arguments: '--configuration Release --no-restore'
- task: DotNetCoreCLI@2
displayName: 'Publish web app'
inputs:
command: 'publish'
projects: 'src/WebApp/*.csproj'
arguments: '--configuration Release --output $(Build.ArtifactStagingDirectory)'
- publish: $(Build.ArtifactStagingDirectory)
artifact: 'webapp'
displayName: 'Upload build artifact'
Cross-Project Package Consumption
When consuming packages from a feed in a different project, ensure:
- The feed is organization-scoped (or the consuming project has explicit access)
- The consuming pipeline's build service identity has Reader role on the feed
- Add the feed as a service connection if using project-scoped feeds across projects
Exercises
Create a Feed & Publish a NuGet Package
Goal: Create an Azure Artifacts feed, publish a NuGet package from a pipeline, and consume it in a separate project.
- Create an organization-scoped feed named
bootcamp-feed. - Create a simple .NET class library (e.g.,
StringUtilswith aCapitalize()method). - Write a pipeline that builds, packs, and publishes the NuGet package to your feed.
- Create a second project with a .NET console app that references
StringUtils. - Add a
nuget.configpointing to your feed and verifydotnet restoresucceeds. - Call
StringUtils.Capitalize("hello")and verify it returns"Hello".
Success criteria: The consuming project builds successfully using the package from your private feed. The package appears in the feed with correct version metadata.
Configure Upstream Sources & Verify Caching
Goal: Set up upstream sources for npm, install a public package through your feed, and verify it's cached.
- Create a feed with npmjs.org as an upstream source (or verify it exists on your existing feed).
- Create a Node.js project with a
.npmrcpointing exclusively to your feed. - Run
npm install lodash— the package should resolve through your feed. - Check your feed in the Azure DevOps web UI — verify
lodashappears as a cached upstream package. - Temporarily disable network access to npmjs.org (or disconnect from internet) and run
npm installagain from a cleannode_modules. - The install should succeed because the package is served from your feed's cache.
Success criteria: Public packages are cached in your feed. Package installs succeed even when the upstream registry is unreachable (using the cached version).
Implement Semantic Versioning with Pre-Release Promotions
Goal: Build a versioning workflow where CI publishes pre-release packages and releases are promoted to @Release view.
- Modify your NuGet pipeline to publish pre-release versions on feature branches (e.g.,
1.0.0-ci.BuildId). - Publish stable versions only from the
mainbranch (e.g.,1.0.BuildId). - After a successful main-branch build, promote the package to the @Release view using the REST API or CLI.
- Configure a consuming project to use the
@Releaseview URL — verify it only sees promoted versions. - Publish a pre-release version and confirm it does NOT appear in the @Release view consumer.
Success criteria: @Release view only contains explicitly promoted versions. Pre-release versions are isolated to @Local. Consumers on @Release see only stable, tested packages.
Retention Policies & Universal Package for ML Model
Goal: Configure retention policies to manage feed bloat, and publish a Universal Package for a large binary artifact.
- Configure retention on your feed: max 10 versions per package, 14-day download protection.
- Publish 12 versions of a test package rapidly (use a loop in a pipeline).
- Wait for the retention job to run (or trigger manually) — verify the oldest 2 versions are cleaned up.
- Promote one version to @Release and verify it's protected from retention cleanup.
- Create a folder with a dummy ML model file (any file > 50 MB) and publish it as a Universal Package.
- Download the Universal Package in a second pipeline using the
UniversalPackages@0task.
Success criteria: Retention policies keep only 10 versions (plus protected ones). The Universal Package publishes and downloads successfully regardless of file size.
Next in the Bootcamp
In Module 10: Security & Governance, we'll cover permissions, policies, audit logs, compliance features, service connections security, secret management, and organizational governance patterns for Azure DevOps at enterprise scale.