关于 dotnet-gha-publish
dotnet-gha-publish 是通过 GitHub Actions 发布 .NET 制品的技能,涵盖 NuGet 包和容器镜像、签名、SBOM 生成,以及由标签或 GitHub Release 触发的发布。
主要功能
- 01向 nuget.org 和 GitHub Packages 发布 NuGet
- 02构建并推送容器镜像
- 03NuGet 与容器镜像签名
- 04生成软件物料清单(SBOM)
- 05按标签或 GitHub Release 条件发布
适用场景
- 01在推送版本标签时发布 NuGet 包
- 02将容器镜像推送到 GHCR、DockerHub 或 ACR
- 03为发布制品配置签名和 SBOM
- 04以 GitHub Release 发布作为人工审批门槛
安装
复制下面的命令,在终端中运行。
npx skills add https:/ / github.com/ wshaddix/ dotnet-skills --skill dotnet-gha-publish安装后在 Claude Code 中输入/dotnet-gha-publish确认已加载。
详细介绍
dotnet-gha-publish
在 GitHub Actions 中为 .NET 项目配置发布工作流:将 NuGet 包推送到 nuget.org 和 GitHub Packages,构建容器映像并推送到 GHCR/DockerHub/ACR,使用 NuGet signing 和 sigstore 对制品签名,使用 Microsoft SBOM tool 生成 SBOM,以及由标签和发布触发的条件发布。
**版本假设:**对于 .NET 8/9/10,使用 actions/setup-dotnet@v4。构建容器映像时使用 docker/build-push-action@v6。进行注册表身份验证时使用 docker/login-action@v3。对于无需 Dockerfile 的容器构建,使用 .NET SDK 容器发布(使用 PublishContainer 的 dotnet publish)。
**范围界定:**此技能负责通过 GitHub Actions 发布制品的工作流。容器映像编写(Dockerfile 最佳实践、SDK 容器属性)由 [skill:dotnet-containers] 负责。Native AOT 编译配置由 [skill:dotnet-native-aot] 负责——此技能仅在 CI 流水线配置中引用 AOT。CLI 专用发布流水线(用于 CLI 二进制文件的 build-package-release)由 [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]。Azure DevOps 发布——请参阅 [skill:dotnet-ado-publish]。部署到目标环境——请参阅 [skill:dotnet-gha-deploy]。
交叉引用:[skill:dotnet-containers] 涵盖容器映像编写和 SDK 容器属性;[skill:dotnet-native-aot] 涵盖 CI 中的 AOT 发布配置;[skill:dotnet-cli-release-pipeline] 涵盖 CLI 专用发布自动化;[skill:dotnet-add-ci] 涵盖入门发布模板。
推送 NuGet 包到 nuget.org
由标签触发的包发布
name: Publish NuGet Package
on:
push:
tags:
- 'v*'
permissions:
contents: read
jobs:
publish:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Setup .NET
uses: actions/setup-dotnet@v4
with:
dotnet-version: '8.0.x'
- name: Extract version from tag
id: version
shell: bash
run: |
set -euo pipefail
VERSION="${GITHUB_REF_NAME#v}"
echo "version=$VERSION" >> "$GITHUB_OUTPUT"
- name: Pack
run: |
set -euo pipefail
dotnet pack src/MyLibrary/MyLibrary.csproj \
-c Release \
-p:Version=${{ steps.version.outputs.version }} \
-o ./nupkgs
- name: Push to nuget.org
run: |
set -euo pipefail
dotnet nuget push ./nupkgs/*.nupkg \
--api-key ${{ secrets.NUGET_API_KEY }} \
--source https://api.nuget.org/v3/index.json \
--skip-duplicate
--skip-duplicate 标志可避免在包版本已发布时失败(支持幂等重试)。
发布到 GitHub Packages
- name: Push to GitHub Packages
run: |
set -euo pipefail
dotnet nuget push ./nupkgs/*.nupkg \
--api-key ${{ secrets.GITHUB_TOKEN }} \
--source https://nuget.pkg.github.com/${{ github.repository_owner }}/index.json \
--skip-duplicate
发布到两个源
发布到 nuget.org 供公众使用,并发布到 GitHub Packages 供组织内部预发布使用:
- name: Push to nuget.org (stable releases)
if: "!contains(steps.version.outputs.version, '-')"
run: |
set -euo pipefail
dotnet nuget push ./nupkgs/*.nupkg \
--api-key ${{ secrets.NUGET_API_KEY }} \
--source https://api.nuget.org/v3/index.json \
--skip-duplicate
- name: Push to GitHub Packages (all versions)
run: |
set -euo pipefail
dotnet nuget push ./nupkgs/*.nupkg \
--api-key ${{ secrets.GITHUB_TOKEN }} \
--source https://nuget.pkg.github.com/${{ github.repository_owner }}/index.json \
--skip-duplicate
预发布版本(包含 -,例如 1.2.3-preview.1)仅推送到 GitHub Packages;稳定版本则推送到两个源。
构建并推送容器映像
使用 docker/build-push-action 基于 Dockerfile 构建
对于使用自定义 Dockerfile 的项目——Dockerfile 编写指南请参阅 [skill:dotnet-containers]:
name: Publish Container Image
on:
push:
tags:
- 'v*'
permissions:
contents: read
packages: write
jobs:
container:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Log in to GHCR
uses: docker/login-action@v3
with:
registry: ghcr.io
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}
- name: Extract metadata
id: meta
uses: docker/metadata-action@v5
with:
images: ghcr.io/${{ github.repository }}
tags: |
type=semver,pattern={{version}}
type=semver,pattern={{major}}.{{minor}}
type=sha
- name: Build and push
uses: docker/build-push-action@v6
with:
context: .
push: true
tags: ${{ steps.meta.outputs.tags }}
labels: ${{ steps.meta.outputs.labels }}
SDK 容器发布(无需 Dockerfile)
对于不使用 Dockerfile 的项目,使用 .NET SDK 容器发布——PublishContainer MSBuild 配置请参阅 [skill:dotnet-containers]:
- name: Setup .NET
uses: actions/setup-dotnet@v4
with:
dotnet-version: '8.0.x'
- name: Log in to GHCR
run: |
set -euo pipefail
echo "${{ secrets.GITHUB_TOKEN }}" | \
docker login ghcr.io -u ${{ github.actor }} --password-stdin
- name: Publish container image
run: |
set -euo pipefail
VERSION="${GITHUB_REF_NAME#v}"
dotnet publish src/MyApp/MyApp.csproj \
-c Release \
-p:PublishProfile=DefaultContainer \
-p:ContainerRegistry=ghcr.io \
-p:ContainerRepository=${{ github.repository }} \
-p:ContainerImageTags="\"${VERSION};latest\""
推送到多个注册表
在同一工作流中推送到 GHCR 和 DockerHub:
- name: Log in to GHCR
uses: docker/login-action@v3
with:
registry: ghcr.io
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}
- name: Log in to DockerHub
uses: docker/login-action@v3
with:
username: ${{ secrets.DOCKERHUB_USERNAME }}
password: ${{ secrets.DOCKERHUB_TOKEN }}
- name: Extract metadata
id: meta
uses: docker/metadata-action@v5
with:
images: |
ghcr.io/${{ github.repository }}
${{ secrets.DOCKERHUB_USERNAME }}/${{ github.event.repository.name }}
tags: |
type=semver,pattern={{version}}
- name: Build and push to both registries
uses: docker/build-push-action@v6
with:
context: .
push: true
tags: ${{ steps.meta.outputs.tags }}
labels: ${{ steps.meta.outputs.labels }}
推送到 Azure Container Registry(ACR)
- name: Log in to ACR
uses: docker/login-action@v3
with:
registry: ${{ secrets.ACR_LOGIN_SERVER }}
username: ${{ secrets.ACR_USERNAME }}
password: ${{ secrets.ACR_PASSWORD }}
- name: Build and push to ACR
uses: docker/build-push-action@v6
with:
context: .
push: true
tags: ${{ secrets.ACR_LOGIN_SERVER }}/myapp:${{ github.ref_name }}
Native AOT 容器发布
将 Native AOT 二进制文件发布为容器映像。AOT 配置由 [skill:dotnet-native-aot] 负责;此处仅展示 CI 流水线步骤:
- name: Publish AOT container
run: |
set -euo pipefail
dotnet publish src/MyApp/MyApp.csproj \
-c Release \
-r linux-x64 \
-p:PublishAot=true \
-p:PublishProfile=DefaultContainer \
-p:ContainerRegistry=ghcr.io \
-p:ContainerRepository=${{ github.repository }} \
-p:ContainerBaseImage=mcr.microsoft.com/dotnet/runtime-deps:8.0-noble-chiseled
对于 AOT 二进制文件,runtime-deps 基础映像已足够,因为它们已包含运行时。AOT MSBuild 属性请参阅 [skill:dotnet-native-aot],基础映像选择请参阅 [skill:dotnet-containers]。
制品签名
NuGet 包签名
使用证书对 NuGet 包签名,以便检测篡改:
- name: Sign NuGet packages
run: |
set -euo pipefail
dotnet nuget sign ./nupkgs/*.nupkg \
--certificate-path ${{ runner.temp }}/signing-cert.pfx \
--certificate-password ${{ secrets.CERT_PASSWORD }} \
--timestamper http://timestamp.digicert.com
在 CI 中,从 base64 编码的机密中提取证书:
- name: Decode signing certificate
shell: bash
run: |
set -euo pipefail
echo "${{ secrets.SIGNING_CERT_BASE64 }}" | base64 -d > "${{ runner.temp }}/signing-cert.pfx"
- name: Sign NuGet packages
run: |
set -euo pipefail
dotnet nuget sign ./nupkgs/*.nupkg \
--certificate-path ${{ runner.temp }}/signing-cert.pfx \
--certificate-password ${{ secrets.CERT_PASSWORD }} \
--timestamper http://timestamp.digicert.com
- name: Clean up certificate
if: always()
run: rm -f "${{ runner.temp }}/signing-cert.pfx"
使用 Sigstore 对容器映像签名
通过 sigstore/cosign 使用无密钥签名对容器映像签名:
- name: Install cosign
uses: sigstore/cosign-installer@v3
- name: Sign container image
env:
COSIGN_EXPERIMENTAL: '1'
run: |
set -euo pipefail
cosign sign --yes ghcr.io/${{ github.repository }}@${{ steps.build.outputs.digest }}
无密钥签名使用 GitHub 的 OIDC 令牌——无需管理私钥。
SBOM 生成
Microsoft SBOM Tool
生成软件物料清单(SBOM),提高供应链透明度:
- name: Generate SBOM
uses: microsoft/sbom-action@v0
with:
BuildDropPath: ./nupkgs
PackageName: MyLibrary
PackageVersion: ${{ steps.version.outputs.version }}
NamespaceUriBase: https://github.com/${{ github.repository }}
- name: Upload SBOM
uses: actions/upload-artifact@v4
with:
name: sbom-${{ steps.version.outputs.version }}
path: ./nupkgs/_manifest/
retention-days: 365
为容器映像生成 SBOM
- name: Generate container SBOM
uses: anchore/sbom-action@v0
with:
image: ghcr.io/${{ github.repository }}:${{ steps.version.outputs.version }}
artifact-name: container-sbom
output-file: container-sbom.spdx.json
将 SBOM 附加到 GitHub Release
- name: Create GitHub Release with SBOM
uses: softprops/action-gh-release@v2
with:
files: |
./nupkgs/*.nupkg
./nupkgs/_manifest/spdx_2.2/manifest.spdx.json
generate_release_notes: true
基于标签和发布的条件式发布
标签模式匹配
on:
push:
tags:
- 'v[0-9]+.[0-9]+.[0-9]+' # stable: v1.2.3
- 'v[0-9]+.[0-9]+.[0-9]+-*' # pre-release: v1.2.3-preview.1
jobs:
publish:
runs-on: ubuntu-latest
steps:
- name: Determine release type
id: release-type
shell: bash
run: |
set -euo pipefail
VERSION="${GITHUB_REF_NAME#v}"
if [[ "$VERSION" == *-* ]]; then
echo "prerelease=true" >> "$GITHUB_OUTPUT"
else
echo "prerelease=false" >> "$GITHUB_OUTPUT"
fi
echo "version=$VERSION" >> "$GITHUB_OUTPUT"
由发布触发的发布
仅在创建 GitHub Release 时发布(提供手动审批关卡):
on:
release:
types: [published]
jobs:
publish:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
with:
ref: ${{ github.event.release.tag_name }}
- name: Extract version
id: version
shell: bash
run: |
set -euo pipefail
VERSION="${{ github.event.release.tag_name }}"
VERSION="${VERSION#v}"
echo "version=$VERSION" >> "$GITHUB_OUTPUT"
- name: Pack and publish
run: |
set -euo pipefail
dotnet pack -c Release -p:Version=${{ steps.version.outputs.version }} -o ./nupkgs
dotnet nuget push ./nupkgs/*.nupkg \
--api-key ${{ secrets.NUGET_API_KEY }} \
--source https://api.nuget.org/v3/index.json \
--skip-duplicate
Agent 注意事项
- 始终在
dotnet nuget push中使用--skip-duplicate—— 否则,重新运行已发布版本的发布工作流会导致作业失败,而非保持幂等。 - 切勿在工作流文件中硬编码 API 密钥 —— 所有凭据都应使用
${{ secrets.NUGET_API_KEY }}或环境范围的机密。 - 在所有多行 bash 步骤中使用
set -euo pipefail—— 如果没有pipefail,管道命令中的失败不会向外传递,导致 CI 错误地显示成功。 - 在
if: always()步骤中清理签名证书 —— 即使作业失败,也必须删除含有私钥材料的临时文件。 - SDK 容器发布需要 Docker 守护进程 —— 使用
PublishProfile=DefaultContainer执行dotnet publish需要在运行器上安装 Docker;请使用已包含 Docker 的ubuntu-latest。 - AOT 发布需要匹配的 RID ——
dotnet publish -r linux-x64必须与运行器操作系统匹配;请勿在ubuntu-latest上使用-r win-x64。 - 由标签触发的工作流不会在拉取请求上运行 —— 从 PR 推送的标签仍会触发工作流;如有需要,可使用
if: github.ref_type == 'tag'作为额外保护条件。 - GHCR 身份验证使用
GITHUB_TOKEN,而非 PAT —— 对于公共仓库,packages: write权限已足够;只有跨仓库访问才需要 PAT。