dotnet-gha-publish について
dotnet-gha-publishは、GitHub Actionsで.NET成果物を公開するためのスキルです。NuGetパッケージやコンテナイメージ、署名、SBOM生成、タグやGitHub Releaseを契機とする公開を扱います。
主な機能
- 01nuget.orgとGitHub PackagesへのNuGet公開
- 02コンテナイメージのビルドとプッシュ
- 03NuGetとコンテナイメージの署名
- 04SBOMの生成
- 05タグまたはGitHub Releaseに応じた公開
活用シーン
- 01バージョンタグのプッシュ時にNuGetパッケージを公開
- 02GHCR、DockerHub、ACRへのコンテナイメージのプッシュ
- 03公開成果物の署名とSBOMの設定
- 04GitHub 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 署名と sigstore による成果物への署名、Microsoft SBOM ツールによる 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] を参照してください。
相互参照:コンテナーイメージの作成と SDK コンテナーのプロパティについては [skill:dotnet-containers]、CI での AOT 公開設定については [skill:dotnet-native-aot]、CLI 専用のリリース自動化については [skill:dotnet-cli-release-pipeline]、公開用スターターテンプレートについては [skill:dotnet-add-ci] を参照してください。
nuget.org への NuGet パッケージのプッシュ
タグをトリガーとしたパッケージの公開
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 リリースに添付
- 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 リリースが作成されたときにのみ公開します(手動承認ゲートを提供します)。
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
エージェント利用時の注意点
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はランナーの OS と一致させる必要があります。ubuntu-latestで-r win-x64を使用しないでください。 - タグをトリガーとするワークフローはプルリクエストでは実行されません — プルリクエストからプッシュされたタグでもワークフローはトリガーされます。必要に応じて、追加のガードとして
if: github.ref_type == 'tag'を使用してください。 - GHCR の認証には PAT ではなく
GITHUB_TOKENを使用します — パブリックリポジトリではpackages: writeの権限で十分です。PAT が必要なのは、リポジトリ間のアクセスを行う場合のみです。