dotnet-gha-deploy について
.NET プロジェクト向けの GitHub Actions デプロイパターン集です。ドキュメントサイト、コンテナレジストリ、Azure Web Apps のデプロイに加え、環境承認、OIDC 認証、ロールバックを扱います。
主な機能
- 01GitHub Pages へのドキュメントデプロイ
- 02GHCR と ACR へのイメージプッシュ
- 03Azure Web Apps のデプロイ
- 04GitHub Environments によるデプロイゲート
- 05デプロイのロールバック戦略
活用シーン
- 01プロジェクトのドキュメントを GitHub Pages に公開
- 02同じコンテナイメージを環境間で昇格
- 03スロットを使った Azure Web Apps のデプロイ
- 04デプロイ失敗後に以前のバージョンへ復元
インストール
コマンドをコピーしてターミナルで実行します。
npx skills add https:/ / github.com/ wshaddix/ dotnet-skills --skill dotnet-gha-deployインストール後、Claude Code で/dotnet-gha-deployを入力して読み込まれたか確認します。
詳細
dotnet-gha-deploy
GitHub Actions における .NET アプリケーションのデプロイパターン:ドキュメントサイト(Starlight/Docusaurus)の GitHub Pages へのデプロイ、GHCR および ACR へのコンテナレジストリプッシュのパターン、azure/webapps-deploy を使用した Azure Web Apps へのデプロイ、段階的なロールアウトの保護ルールを設定した GitHub Environments、デプロイ失敗時のロールバック戦略。
バージョンの前提条件: GitHub Actions ワークフロー構文 v2。Azure App Service には azure/webapps-deploy@v3 を使用します。Azure の認証情報管理には azure/login@v2 を使用します。デプロイゲートには GitHub Environments を使用します。
スコープの境界: このスキルは、GitHub Actions のデプロイパイプラインパターンを担当します。コンテナオーケストレーションとランタイム構成は [skill:dotnet-container-deployment] が担当します。コンテナイメージの作成は [skill:dotnet-containers] が担当します。パブリッシュ(NuGet へのプッシュ、コンテナのビルド)は [skill:dotnet-gha-publish] が担当します。コンポーザブルなワークフローパターンは [skill:dotnet-gha-patterns] にあります。スターター CI テンプレートは [skill:dotnet-add-ci] が担当します。
対象外: コンテナオーケストレーション(Kubernetes、Docker Compose)については、[skill:dotnet-container-deployment] を参照してください。コンテナイメージの作成については、[skill:dotnet-containers] を参照してください。NuGet のパブリッシュとコンテナのビルドについては、[skill:dotnet-gha-publish] を参照してください。スターター CI テンプレートについては、[skill:dotnet-add-ci] を参照してください。Azure DevOps のデプロイについては、[skill:dotnet-ado-patterns] を参照してください。CLI のリリースパイプラインについては、[skill:dotnet-cli-release-pipeline] を参照してください。
相互参照:コンテナオーケストレーションパターンについては [skill:dotnet-container-deployment]、コンテナイメージの作成については [skill:dotnet-containers]、スターター CI テンプレートについては [skill:dotnet-add-ci]、CLI 固有のリリース自動化については [skill:dotnet-cli-release-pipeline] を参照してください。
ドキュメントの GitHub Pages へのデプロイ
静的サイトのデプロイ(Starlight/Docusaurus)
.NET プロジェクトのドキュメントサイトを GitHub Pages にデプロイします。
name: Deploy Docs
on:
push:
branches: [main]
paths:
- 'docs/**'
- '.github/workflows/deploy-docs.yml'
workflow_dispatch:
permissions:
contents: read
pages: write
id-token: write
concurrency:
group: pages
cancel-in-progress: false
jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Setup Node.js
uses: actions/setup-node@v4
with:
node-version: '20'
cache: npm
cache-dependency-path: docs/package-lock.json
- name: Install dependencies
working-directory: docs
run: npm ci
- name: Build documentation site
working-directory: docs
run: npm run build
- name: Upload Pages artifact
uses: actions/upload-pages-artifact@v3
with:
path: docs/dist
deploy:
needs: build
runs-on: ubuntu-latest
environment:
name: github-pages
url: ${{ steps.deployment.outputs.page_url }}
steps:
- name: Deploy to GitHub Pages
id: deployment
uses: actions/deploy-pages@v4
主な判断事項:
concurrency.cancel-in-progress: falseにより、進行中の Pages デプロイがキャンセルされないようにします- Pages デプロイトークンには
id-token: write権限が必要です buildジョブとdeployジョブを分けることで、デプロイジョブに保護ルール付きのgithub-pages環境を使用できます
XML コメントからの API ドキュメント生成
.NET の XML コメントから API リファレンスドキュメントを生成してデプロイします。
- name: Setup .NET
uses: actions/setup-dotnet@v4
with:
dotnet-version: '8.0.x'
- name: Build with XML docs
run: |
set -euo pipefail
dotnet build src/MyLibrary/MyLibrary.csproj \
-c Release \
-p:GenerateDocumentationFile=true
- name: Generate API docs with docfx
run: |
set -euo pipefail
dotnet tool install -g docfx
docfx docs/docfx.json
- name: Upload Pages artifact
uses: actions/upload-pages-artifact@v3
with:
path: docs/_site
コンテナレジストリへのプッシュパターン
環境ゲートを使用した GHCR へのプッシュ
jobs:
build:
runs-on: ubuntu-latest
outputs:
image-digest: ${{ steps.build.outputs.digest }}
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: Build and push
id: build
uses: docker/build-push-action@v6
with:
context: .
push: true
tags: ghcr.io/${{ github.repository }}:${{ github.sha }}
deploy-staging:
needs: build
runs-on: ubuntu-latest
environment:
name: staging
url: https://staging.example.com
steps:
- name: Deploy container to staging
run: |
set -euo pipefail
echo "Deploying ghcr.io/${{ github.repository }}@${{ needs.build.outputs.image-digest }} to staging"
# Platform-specific deployment command here
deploy-production:
needs: deploy-staging
runs-on: ubuntu-latest
environment:
name: production
url: https://example.com
steps:
- name: Deploy container to production
run: |
set -euo pipefail
echo "Deploying ghcr.io/${{ github.repository }}@${{ needs.build.outputs.image-digest }} to production"
ダイジェストによる昇格(不変のデプロイ)
環境間で不変のデプロイを行うには、イメージダイジェストの参照を使用します。
- name: Retag for production
run: |
set -euo pipefail
# Pull by digest (immutable), retag for production
docker pull ghcr.io/${{ github.repository }}@${{ needs.build.outputs.image-digest }}
docker tag ghcr.io/${{ github.repository }}@${{ needs.build.outputs.image-digest }} \
ghcr.io/${{ github.repository }}:production
docker push ghcr.io/${{ github.repository }}:production
ダイジェストベースの昇格により、タグが変更されても、本番環境にはまったく同じイメージのバイト列がデプロイされます。
Azure Web Apps へのデプロイ
azure/webapps-deploy を使用したデプロイ
name: Deploy to Azure
on:
push:
branches: [main]
permissions:
contents: read
id-token: write
jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Setup .NET
uses: actions/setup-dotnet@v4
with:
dotnet-version: '8.0.x'
- name: Publish
run: |
set -euo pipefail
dotnet publish src/MyApp/MyApp.csproj \
-c Release \
-o ./publish
- name: Upload publish artifact
uses: actions/upload-artifact@v4
with:
name: webapp
path: ./publish
deploy-staging:
needs: build
runs-on: ubuntu-latest
environment:
name: staging
url: https://myapp-staging.azurewebsites.net
steps:
- name: Download artifact
uses: actions/download-artifact@v4
with:
name: webapp
path: ./publish
- name: Login to Azure
uses: azure/login@v2
with:
client-id: ${{ secrets.AZURE_CLIENT_ID }}
tenant-id: ${{ secrets.AZURE_TENANT_ID }}
subscription-id: ${{ secrets.AZURE_SUBSCRIPTION_ID }}
- name: Deploy to Azure Web App
uses: azure/webapps-deploy@v3
with:
app-name: myapp-staging
package: ./publish
deploy-production:
needs: deploy-staging
runs-on: ubuntu-latest
environment:
name: production
url: https://myapp.azurewebsites.net
steps:
- name: Download artifact
uses: actions/download-artifact@v4
with:
name: webapp
path: ./publish
- name: Login to Azure
uses: azure/login@v2
with:
client-id: ${{ secrets.AZURE_CLIENT_ID }}
tenant-id: ${{ secrets.AZURE_TENANT_ID }}
subscription-id: ${{ secrets.AZURE_SUBSCRIPTION_ID }}
- name: Deploy to Azure Web App
uses: azure/webapps-deploy@v3
with:
app-name: myapp-production
package: ./publish
デプロイスロットを使用した Azure Web App
事前のスワップ検証を行い、ダウンタイムなしでデプロイするには、デプロイスロットを使用します。
- name: Deploy to staging slot
uses: azure/webapps-deploy@v3
with:
app-name: myapp-production
slot-name: staging
package: ./publish
- name: Validate staging slot
shell: bash
run: |
set -euo pipefail
HTTP_STATUS=$(curl -s -o /dev/null -w "%{http_code}" \
https://myapp-production-staging.azurewebsites.net/healthz)
if [ "$HTTP_STATUS" != "200" ]; then
echo "Health check failed with status $HTTP_STATUS"
exit 1
fi
- name: Swap slots
uses: azure/cli@v2
with:
inlineScript: |
az webapp deployment slot swap \
--resource-group myapp-rg \
--name myapp-production \
--slot staging \
--target-slot production
OIDC 認証(フェデレーション資格情報)
サービスプリンシパルシークレットの代わりに OIDC を使用して、パスワードレスで Azure 認証を行います。
- name: Login to Azure (OIDC)
uses: azure/login@v2
with:
client-id: ${{ secrets.AZURE_CLIENT_ID }}
tenant-id: ${{ secrets.AZURE_TENANT_ID }}
subscription-id: ${{ secrets.AZURE_SUBSCRIPTION_ID }}
OIDC を使用するには、GitHub Actions の OIDC プロバイダーを信頼するフェデレーション資格情報を Azure AD に構成する必要があります。GitHub Secrets にクライアントシークレットを保存する必要はありません。
保護ルール付きの GitHub Environments
複数環境のパイプライン
jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- run: dotnet publish -c Release -o ./publish
- uses: actions/upload-artifact@v4
with:
name: app
path: ./publish
deploy-dev:
needs: build
runs-on: ubuntu-latest
environment: development
steps:
- uses: actions/download-artifact@v4
with:
name: app
- run: echo "Deploy to dev"
deploy-staging:
needs: deploy-dev
runs-on: ubuntu-latest
environment:
name: staging
url: https://staging.example.com
steps:
- uses: actions/download-artifact@v4
with:
name: app
- run: echo "Deploy to staging"
deploy-production:
needs: deploy-staging
runs-on: ubuntu-latest
environment:
name: production
url: https://example.com
steps:
- uses: actions/download-artifact@v4
with:
name: app
- run: echo "Deploy to production"
保護ルールの構成
各環境の GitHub Settings > Environments で構成します。
| 環境 | 必須レビュアー | 待機時間 | ブランチポリシー |
|---|---|---|---|
| development | なし | なし | すべてのブランチ |
| staging | レビュアー 1 人 | なし | main、release/* |
| production | レビュアー 2 人 | 15 分 | main のみ |
環境固有のシークレットと変数
各環境では、リポジトリレベルのシークレットを上書きできます。
jobs:
deploy:
environment: production
runs-on: ubuntu-latest
steps:
- name: Deploy with environment-specific config
env:
# Resolves to the production environment's secret, not the repo-level one
DB_CONNECTION: ${{ secrets.DB_CONNECTION_STRING }}
APP_URL: ${{ vars.APP_URL }}
run: |
set -euo pipefail
echo "Deploying to $APP_URL"
ロールバックパターン
デプロイの取り消し
失敗時には、直前の正常なバージョンを再デプロイします。
jobs:
deploy:
runs-on: ubuntu-latest
environment: production
steps:
- name: Deploy new version
id: deploy
continue-on-error: true
run: |
set -euo pipefail
# Deploy logic here
./deploy.sh --version ${{ github.sha }}
- name: Health check
id: health
if: steps.deploy.outcome == 'success'
continue-on-error: true
shell: bash
run: |
set -euo pipefail
for i in {1..5}; do
HTTP_STATUS=$(curl -s -o /dev/null -w "%{http_code}" https://example.com/healthz)
if [ "$HTTP_STATUS" = "200" ]; then
echo "Health check passed"
exit 0
fi
sleep 10
done
echo "Health check failed after 5 attempts"
exit 1
- name: Rollback on failure
if: steps.deploy.outcome == 'failure' || steps.health.outcome == 'failure'
run: |
set -euo pipefail
echo "Rolling back to previous version"
# Re-deploy the last known-good artifact
./deploy.sh --version ${{ github.event.before }}
- name: Fail the job if rollback was needed
if: steps.deploy.outcome == 'failure' || steps.health.outcome == 'failure'
run: exit 1
Azure デプロイスロットのロールバック
ヘルスチェックが失敗した場合は、前のスロットにスワップし戻します。
- name: Swap to production
id: swap
uses: azure/cli@v2
with:
inlineScript: |
az webapp deployment slot swap \
--resource-group myapp-rg \
--name myapp-production \
--slot staging \
--target-slot production
- name: Post-swap health check
id: post-health
continue-on-error: true
shell: bash
run: |
set -euo pipefail
sleep 30 # allow swap to stabilize
HTTP_STATUS=$(curl -s -o /dev/null -w "%{http_code}" https://myapp.azurewebsites.net/healthz)
if [ "$HTTP_STATUS" != "200" ]; then
echo "Post-swap health check failed"
exit 1
fi
- name: Rollback swap on failure
if: steps.post-health.outcome == 'failure'
uses: azure/cli@v2
with:
inlineScript: |
az webapp deployment slot swap \
--resource-group myapp-rg \
--name myapp-production \
--slot staging \
--target-slot production
echo "Rolled back: swapped staging back to production"
workflow_dispatch による手動ロールバック
緊急ロールバック用の手動トリガーを用意します。
on:
workflow_dispatch:
inputs:
version:
description: 'Version to roll back to (e.g., v1.2.3)'
required: true
type: string
environment:
description: 'Target environment'
required: true
type: choice
options:
- staging
- production
jobs:
rollback:
runs-on: ubuntu-latest
environment: ${{ inputs.environment }}
steps:
- uses: actions/checkout@v4
with:
ref: ${{ inputs.version }}
- name: Publish
run: |
set -euo pipefail
dotnet publish src/MyApp/MyApp.csproj -c Release -o ./publish
- name: Deploy rollback version
run: |
set -euo pipefail
echo "Rolling back ${{ inputs.environment }} to ${{ inputs.version }}"
# Platform-specific deployment
エージェント向けの注意事項
- 複数行のすべての bash ステップで
set -euo pipefailを使用してください --pipefailがないと、パイプ処理されたコマンドの失敗が通知されず、デプロイが成功したように見えてしまいます。 - デプロイの並行処理グループで
cancel-in-progress: trueは使用しないでください -- 進行中のデプロイをキャンセルすると、インフラストラクチャがデプロイ途中の状態になる可能性があります。 - デプロイ後は必ずヘルスチェックを実行してください --
deployステップが成功しても、アプリケーションが正しく動作しているとは限りません。HTTP ヘルスチェックで確認してください。 - OIDC Azure ログインには
id-token: write権限を使用してください -- これがないと、フェデレーション資格情報の交換が、原因のわかりにくい 403 エラーで失敗します。 - デプロイスロットのスワップはアトミックです -- スワップが失敗した場合、両方のスロットには元のデプロイが維持され、中途半端な状態にはなりません。
- ワークフローファイルに Azure の資格情報をハードコードしないでください -- OIDC フェデレーション資格情報、または環境スコープのシークレットを使用してください。YAML にハードコードされたシークレットは、リポジトリの履歴から閲覧できます。
- 本番環境へのデプロイではダイジェストベースのイメージ参照を使用してください -- タグは変更や上書きが可能ですが、ダイジェストは変更できず、イメージのバイト列を正確に保証します。
- ビルドジョブとデプロイジョブを分離してください -- 成果物を一度だけビルドし、同じ成果物を複数の環境にデプロイすることで、一貫性を確保できます。