关于 dotnet-ado-publish
面向 .NET 团队的 Azure DevOps 发布流水线技能,涵盖 NuGet 包、容器镜像和构建产物的发布。内容包括 Azure Artifacts、nuget.org、ACR 与多阶段流水线的配置方式。
主要功能
- 01推送 NuGet 包到 Azure Artifacts
- 02发布包到 nuget.org
- 03构建并推送容器镜像到 ACR
- 04暂存和发布流水线产物
- 05在多阶段流水线间传递产物
适用场景
- 01从 Azure DevOps 发布 NuGet 包
- 02将容器镜像推送到 ACR
- 03为多阶段发布流程传递构建产物
安装
复制下面的命令,在终端中运行。
npx skills add https:/ / github.com/ wshaddix/ dotnet-skills --skill dotnet-ado-publish安装后在 Claude Code 中输入/dotnet-ado-publish确认已加载。
详细介绍
dotnet-ado-publish
Azure DevOps 中 .NET 项目的发布流水线:将 NuGet 包推送到 Azure Artifacts 和 nuget.org,使用 Docker@2 构建容器镜像并推送到 Azure Container Registry (ACR),使用 PublishBuildArtifacts@1 和 PublishPipelineArtifact@1 暂存构建产物,以及在多阶段发布流水线中使用流水线构建产物。
版本假设: 使用 DotNetCoreCLI@2 执行 pack/push 操作。使用 Docker@2 构建容器镜像。使用 NuGetCommand@2 将 NuGet 包推送到外部源。使用 PublishPipelineArtifact@1(优先于 PublishBuildArtifacts@1)。
范围边界: 此技能负责从 Azure DevOps 发布构建产物的流水线。容器镜像编写(Dockerfile 最佳实践、SDK 容器属性)由 [skill:dotnet-containers] 负责。Native AOT 编译配置由 [skill:dotnet-native-aot] 负责——此技能仅涉及 CI 流水线配置中的 AOT。CLI 专用发布流水线由 [skill:dotnet-cli-release-pipeline] 负责。入门 CI 模板由 [skill:dotnet-add-ci] 负责。
范围外: 容器镜像编写(Dockerfile、基础镜像选择)——请参阅 [skill:dotnet-containers]。Native AOT MSBuild 配置——请参阅 [skill:dotnet-native-aot]。CLI 发布流水线——请参阅 [skill:dotnet-cli-release-pipeline]。入门 CI 模板——请参阅 [skill:dotnet-add-ci]。GitHub Actions 发布——请参阅 [skill:dotnet-gha-publish]。ADO 专属功能(环境、服务连接)——请参阅 [skill:dotnet-ado-unique]。
交叉引用:[skill:dotnet-containers](容器镜像编写和 SDK 容器属性)、[skill:dotnet-native-aot](CI 中的 AOT 发布配置)、[skill:dotnet-cli-release-pipeline](CLI 专用发布自动化)、[skill:dotnet-add-ci](入门发布模板)。
将 NuGet 包推送到 Azure Artifacts
使用 DotNetCoreCLI@2 推送
trigger:
tags:
include:
- 'v*'
stages:
- stage: Pack
jobs:
- job: PackJob
pool:
vmImage: 'ubuntu-latest'
steps:
- task: UseDotNet@2
inputs:
packageType: 'sdk'
version: '8.0.x'
- task: DotNetCoreCLI@2
displayName: 'Pack'
inputs:
command: 'pack'
packagesToPack: 'src/**/*.csproj'
configuration: 'Release'
outputDir: '$(Build.ArtifactStagingDirectory)/nupkgs'
versioningScheme: 'byEnvVar'
versionEnvVar: 'PACKAGE_VERSION'
env:
PACKAGE_VERSION: $(Build.SourceBranchName)
- task: PublishPipelineArtifact@1
displayName: 'Upload NuGet packages'
inputs:
targetPath: '$(Build.ArtifactStagingDirectory)/nupkgs'
artifactName: 'nupkgs'
- stage: PushToFeed
dependsOn: Pack
jobs:
- job: PushJob
pool:
vmImage: 'ubuntu-latest'
steps:
- download: current
artifact: nupkgs
- task: NuGetAuthenticate@1
displayName: 'Authenticate NuGet'
- task: DotNetCoreCLI@2
displayName: 'Push to Azure Artifacts'
inputs:
command: 'push'
packagesToPush: '$(Pipeline.Workspace)/nupkgs/*.nupkg'
nuGetFeedType: 'internal'
publishVstsFeed: 'MyProject/MyFeed'
从 Git 标签获取版本
使用脚本步骤从触发流水线的 Git 标签中提取版本。Build.SourceBranch 是运行时变量,因此请使用脚本解析,而不是使用编译时模板表达式:
steps:
- script: |
set -euo pipefail
if [[ "$(Build.SourceBranch)" == refs/tags/v* ]]; then
VERSION="${BUILD_SOURCEBRANCH#refs/tags/v}"
else
VERSION="0.0.0-ci.$(Build.BuildId)"
fi
echo "##vso[task.setvariable variable=packageVersion]$VERSION"
displayName: 'Extract version from tag'
- task: DotNetCoreCLI@2
displayName: 'Pack'
inputs:
command: 'pack'
packagesToPack: 'src/**/*.csproj'
configuration: 'Release'
outputDir: '$(Build.ArtifactStagingDirectory)/nupkgs'
arguments: '-p:Version=$(packageVersion)'
将 NuGet 包推送到 nuget.org
使用 NuGetCommand@2 推送
要将 NuGet 包推送到外部源(nuget.org),请使用服务连接:
- task: NuGetCommand@2
displayName: 'Push to nuget.org'
inputs:
command: 'push'
packagesToPush: '$(Pipeline.Workspace)/nupkgs/*.nupkg'
nuGetFeedType: 'external'
publishFeedCredentials: 'NuGetOrgServiceConnection'
服务连接会安全地存储 nuget.org API 密钥。在“项目设置” > “服务连接” > “NuGet”中创建服务连接。
根据条件推送(稳定版与预发布版)
- task: NuGetCommand@2
displayName: 'Push to nuget.org (stable only)'
condition: and(succeeded(), not(contains(variables['packageVersion'], '-')))
inputs:
command: 'push'
packagesToPush: '$(Pipeline.Workspace)/nupkgs/*.nupkg'
nuGetFeedType: 'external'
publishFeedCredentials: 'NuGetOrgServiceConnection'
- task: DotNetCoreCLI@2
displayName: 'Push to Azure Artifacts (all versions)'
inputs:
command: 'push'
packagesToPush: '$(Pipeline.Workspace)/nupkgs/*.nupkg'
nuGetFeedType: 'internal'
publishVstsFeed: 'MyProject/MyFeed'
预发布版本(包含 -,如 1.2.3-preview.1)仅推送到 Azure Artifacts;稳定版本则推送到两个源。
跳过重复包
- task: DotNetCoreCLI@2
displayName: 'Push (skip duplicates)'
inputs:
command: 'push'
packagesToPush: '$(Pipeline.Workspace)/nupkgs/*.nupkg'
nuGetFeedType: 'internal'
publishVstsFeed: 'MyProject/MyFeed'
continueOnError: true # Azure Artifacts returns 409 for duplicates
Azure Artifacts 会对重复的包版本返回 HTTP 409。可使用 continueOnError: true 以支持流水线幂等重跑,或在源设置中配置允许覆盖预发布版本。
构建容器镜像并推送到 ACR
Docker@2 任务
构建容器镜像并推送到 Azure Container Registry。Dockerfile 编写指南请参阅 [skill:dotnet-containers]:
stages:
- stage: BuildContainer
jobs:
- job: DockerBuild
pool:
vmImage: 'ubuntu-latest'
steps:
- task: Docker@2
displayName: 'Login to ACR'
inputs:
command: 'login'
containerRegistry: 'MyACRServiceConnection'
- task: Docker@2
displayName: 'Build and push'
inputs:
command: 'buildAndPush'
repository: 'myapp'
containerRegistry: 'MyACRServiceConnection'
dockerfile: 'src/MyApp/Dockerfile'
buildContext: '.'
tags: |
$(Build.BuildId)
latest
标签策略
- task: Docker@2
displayName: 'Build and push with semver tags'
inputs:
command: 'buildAndPush'
repository: 'myapp'
containerRegistry: 'MyACRServiceConnection'
dockerfile: 'src/MyApp/Dockerfile'
buildContext: '.'
tags: |
$(packageVersion)
$(Build.SourceVersion)
latest
发布镜像使用语义化版本标签,便于追踪的镜像使用提交 SHA 标签。latest 标签仅应应用于稳定版发布。
使用 SDK 发布容器(无需 Dockerfile)
对于没有 Dockerfile 的项目,可使用 .NET SDK 发布容器。PublishContainer MSBuild 配置请参阅 [skill:dotnet-containers]:
- task: Docker@2
displayName: 'Login to ACR'
inputs:
command: 'login'
containerRegistry: 'MyACRServiceConnection'
- task: UseDotNet@2
inputs:
packageType: 'sdk'
version: '8.0.x'
- script: |
dotnet publish src/MyApp/MyApp.csproj \
-c Release \
-p:PublishProfile=DefaultContainer \
-p:ContainerRegistry=$(ACR_LOGIN_SERVER) \
-p:ContainerRepository=myapp \
-p:ContainerImageTags='"$(packageVersion);latest"'
displayName: 'Publish container via SDK'
env:
ACR_LOGIN_SERVER: $(acrLoginServer)
发布 Native AOT 容器
将 Native AOT 二进制文件发布为容器镜像。AOT 配置由 [skill:dotnet-native-aot] 负责;此处仅展示 CI 流水线步骤:
- script: |
dotnet publish src/MyApp/MyApp.csproj \
-c Release \
-r linux-x64 \
-p:PublishAot=true \
-p:PublishProfile=DefaultContainer \
-p:ContainerRegistry=$(ACR_LOGIN_SERVER) \
-p:ContainerRepository=myapp \
-p:ContainerBaseImage=mcr.microsoft.com/dotnet/runtime-deps:8.0-noble-chiseled \
-p:ContainerImageTags='"$(packageVersion)"'
displayName: 'Publish AOT container'
AOT 二进制文件已包含运行时,因此使用 runtime-deps 基础镜像即可。AOT MSBuild 属性请参阅 [skill:dotnet-native-aot],基础镜像选择请参阅 [skill:dotnet-containers]。
构建产物暂存
PublishPipelineArtifact@1(推荐)
流水线构建产物是新一代构建产物,可提供更快的上传和下载速度,并支持重复数据删除:
steps:
- task: DotNetCoreCLI@2
displayName: 'Publish app'
inputs:
command: 'publish'
projects: 'src/MyApp/MyApp.csproj'
arguments: '-c Release -o $(Build.ArtifactStagingDirectory)/app'
- task: PublishPipelineArtifact@1
displayName: 'Upload app artifact'
inputs:
targetPath: '$(Build.ArtifactStagingDirectory)/app'
artifactName: 'app'
- task: PublishPipelineArtifact@1
displayName: 'Upload NuGet packages'
inputs:
targetPath: '$(Build.ArtifactStagingDirectory)/nupkgs'
artifactName: 'nupkgs'
PublishBuildArtifacts@1(旧版)
仅在与需要生成构建项目的经典发布管道集成时使用:
- task: PublishBuildArtifacts@1
displayName: 'Upload build artifact (legacy)'
inputs:
pathToPublish: '$(Build.ArtifactStagingDirectory)/app'
artifactName: 'app'
publishLocation: 'Container'
在下游阶段下载项目
stages:
- stage: Build
jobs:
- job: BuildJob
steps:
- script: dotnet publish -c Release -o $(Build.ArtifactStagingDirectory)/app
- task: PublishPipelineArtifact@1
inputs:
targetPath: '$(Build.ArtifactStagingDirectory)/app'
artifactName: 'app'
- stage: Deploy
dependsOn: Build
jobs:
- deployment: DeployJob
environment: 'staging'
strategy:
runOnce:
deploy:
steps:
- download: current
artifact: app
- script: echo "Deploying from $(Pipeline.Workspace)/app"
download: current 关键字会从当前管道运行中下载项目。对于来自其他管道的项目,请使用 download: pipelineName。
用于发布管道的管道项目
使用项目提升的多阶段发布
trigger:
tags:
include:
- 'v*'
stages:
- stage: Build
jobs:
- job: BuildAndPack
pool:
vmImage: 'ubuntu-latest'
steps:
- task: UseDotNet@2
inputs:
packageType: 'sdk'
version: '8.0.x'
- task: DotNetCoreCLI@2
displayName: 'Build'
inputs:
command: 'build'
projects: 'MyApp.sln'
arguments: '-c Release'
- task: DotNetCoreCLI@2
displayName: 'Publish'
inputs:
command: 'publish'
projects: 'src/MyApp/MyApp.csproj'
arguments: '-c Release -o $(Build.ArtifactStagingDirectory)/app'
- task: DotNetCoreCLI@2
displayName: 'Pack'
inputs:
command: 'pack'
packagesToPack: 'src/MyLibrary/MyLibrary.csproj'
configuration: 'Release'
outputDir: '$(Build.ArtifactStagingDirectory)/nupkgs'
- task: PublishPipelineArtifact@1
displayName: 'Upload app'
inputs:
targetPath: '$(Build.ArtifactStagingDirectory)/app'
artifactName: 'app'
- task: PublishPipelineArtifact@1
displayName: 'Upload packages'
inputs:
targetPath: '$(Build.ArtifactStagingDirectory)/nupkgs'
artifactName: 'nupkgs'
- stage: DeployStaging
dependsOn: Build
jobs:
- deployment: DeployStaging
environment: 'staging'
pool:
vmImage: 'ubuntu-latest'
strategy:
runOnce:
deploy:
steps:
- download: current
artifact: app
- script: echo "Deploying to staging from $(Pipeline.Workspace)/app"
- stage: PublishPackages
dependsOn: DeployStaging
jobs:
- job: PushPackages
pool:
vmImage: 'ubuntu-latest'
steps:
- download: current
artifact: nupkgs
- task: NuGetAuthenticate@1
- task: NuGetCommand@2
displayName: 'Push to nuget.org'
inputs:
command: 'push'
packagesToPush: '$(Pipeline.Workspace)/nupkgs/*.nupkg'
nuGetFeedType: 'external'
publishFeedCredentials: 'NuGetOrgServiceConnection'
- stage: DeployProduction
dependsOn: DeployStaging
condition: and(succeeded(), eq(variables['Build.SourceBranch'], 'refs/heads/main'))
jobs:
- deployment: DeployProduction
environment: 'production'
pool:
vmImage: 'ubuntu-latest'
strategy:
runOnce:
deploy:
steps:
- download: current
artifact: app
- script: echo "Deploying to production from $(Pipeline.Workspace)/app"
跨管道使用项目
使用来自其他管道(例如共享构建管道)的项目:
resources:
pipelines:
- pipeline: buildPipeline
source: 'MyApp-Build'
trigger:
branches:
include:
- main
stages:
- stage: Deploy
jobs:
- deployment: DeployFromBuild
environment: 'staging'
strategy:
runOnce:
deploy:
steps:
- download: buildPipeline
artifact: app
- script: echo "Deploying from $(Pipeline.Workspace)/buildPipeline/app"
Agent 注意事项
- 优先使用
PublishPipelineArtifact@1,而不是PublishBuildArtifacts@1——管道项目速度更快、支持重复数据删除,并且适用于多阶段 YAML 管道;构建项目已过时,仅经典发布管道需要使用。 - Azure Artifacts 遇到重复的包版本时会返回 409 ——可使用
continueOnError: true支持幂等重跑,或在源设置中允许覆盖预发布版本,以处理重复项。 NuGetCommand@2使用external源类型时需要服务连接 ——不要在管道 YAML 中硬编码 API 密钥;请在“项目设置”中创建 NuGet 服务连接,以安全存储密钥。- SDK 容器发布需要 Agent 上安装 Docker ——使用
PublishProfile=DefaultContainer的dotnet publish需要 Docker;托管的ubuntu-latestAgent 已包含 Docker,但可自托管的 Agent 可能没有。 - AOT 发布要求 RID 匹配 ——
dotnet publish -r linux-x64必须与 Agent 操作系统匹配;不要在 Linux Agent 上使用-r win-x64。 download: current使用的是$(Pipeline.Workspace),而非$(Build.ArtifactStagingDirectory)——在部署作业中下载的项目位于$(Pipeline.Workspace)/artifactName,而不是暂存目录。- 切勿在管道 YAML 中硬编码注册表凭据 ——使用 Docker 服务连接进行 ACR/DockerHub 身份验证;服务连接会安全存储凭据,并可单独轮换。
- 标记触发器需要在触发器部分显式设置
tags.include——默认 CI 触发器不包含标记;添加tags: include: ['v*']以在版本标记上触发。