mcpField
D
dotnet-gha-publish

dotnet-gha-publish

github.com/wshaddix云与 DevOps8670 次安装35 周前更新
代码仓库

关于 dotnet-gha-publish

dotnet-gha-publish 是通过 GitHub Actions 发布 .NET 制品的技能,涵盖 NuGet 包和容器镜像、签名、SBOM 生成,以及由标签或 GitHub Release 触发的发布。

主要功能

  1. 01向 nuget.org 和 GitHub Packages 发布 NuGet
  2. 02构建并推送容器镜像
  3. 03NuGet 与容器镜像签名
  4. 04生成软件物料清单(SBOM)
  5. 05按标签或 GitHub Release 条件发布

适用场景

  1. 01在推送版本标签时发布 NuGet 包
  2. 02将容器镜像推送到 GHCR、DockerHub 或 ACR
  3. 03为发布制品配置签名和 SBOM
  4. 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 注意事项

  1. 始终在 dotnet nuget push 中使用 --skip-duplicate —— 否则,重新运行已发布版本的发布工作流会导致作业失败,而非保持幂等。
  2. 切勿在工作流文件中硬编码 API 密钥 —— 所有凭据都应使用 ${{ secrets.NUGET_API_KEY }} 或环境范围的机密。
  3. 在所有多行 bash 步骤中使用 set -euo pipefail —— 如果没有 pipefail,管道命令中的失败不会向外传递,导致 CI 错误地显示成功。
  4. 在 if: always() 步骤中清理签名证书 —— 即使作业失败,也必须删除含有私钥材料的临时文件。
  5. SDK 容器发布需要 Docker 守护进程 —— 使用 PublishProfile=DefaultContainer 执行 dotnet publish 需要在运行器上安装 Docker;请使用已包含 Docker 的 ubuntu-latest。
  6. AOT 发布需要匹配的 RID —— dotnet publish -r linux-x64 必须与运行器操作系统匹配;请勿在 ubuntu-latest 上使用 -r win-x64。
  7. 由标签触发的工作流不会在拉取请求上运行 —— 从 PR 推送的标签仍会触发工作流;如有需要,可使用 if: github.ref_type == 'tag' 作为额外保护条件。
  8. GHCR 身份验证使用 GITHUB_TOKEN,而非 PAT —— 对于公共仓库,packages: write 权限已足够;只有跨仓库访问才需要 PAT。

评论

支持 Markdown