はじめに
今回は、前回の続きとなります。
AWSコンソール上で、手動でフロントエンドを更新するのは大変 && ミスが発生しやすいため、今回はGitHub Actionsを導入して更新作業を自動化してみました。
前提
-
Nuxt 4 + pnpm 11を使用する -
pnpm generateで.output/public/にSPAを出力し、環境変数NUXT_PUBLIC_LIFF_IDをビルド時に渡す - 「任意のブランチ」を「任意のAWS環境(dev/stg/prod)」にデプロイできるようにする
GitHub Actionsの設計原則
※2026年5月時点で、世界標準で多く採用されているベストプラクティスを参考にしました。
- OIDCでAWS認証(長期IAMユーザーキー禁止)
→ アクセスキー漏洩リスクをゼロにする - GitHub Environmentsを3つ作成(dev/stg/prod)
→ 環境ごとに変数・承認者・デプロイ可能ブランチを分離する - IAM ロール側の信頼ポリシーで
environment:xxxを直接バインド
→ 「prodロールはprod Environment経由でしかassumeできない」を保証する -
workflow_dispatchでtarget_envとrefを選択(今回はrefは未使用)
→ 任意のブランチを任意の環境に任意のタイミングで手動デプロイできるようにする - prodはRequired Reviewers + main限定(GitHub Environment設定)
→ 本番環境はヒューマンエラーを防止する(今回はRequired Reviewersは未使用) -
permissions:をジョブ単位で最小化(id-token: write,contents: readのみ)
→ GITHUB_TOKEN経由の権限濫用を防止(セキュリティ) - アクション・ロックファイル固定、fail-fast, concurrency
→ 再現性・並行デプロイ事故防止 - 長期キャッシュアセット(
_nuxt/*)とHTML群を2段階でaws s3 sync
→ ユーザー体験とデプロイの高速化(今回は/*にしています) - CloudFront InvalidationはHTML群のみ(ハッシュ付きアセットは不要)
→ コスト最適化(無料枠 1000path/月)(今回は/*にしています)
ワークフロー(処理)の流れ
対象のGitHubリポジトリ
↓
GitHub Actions → "Run workflow"
inputs: branch=feature/xxx, target_env=stg
↓
----------------------------------------
GitHub Actions Runner
- checkout(branch)
- pnpm install + generate
- OIDC で AssumeRole (stg ロール)
- aws s3 sync (2段)
- cloudfront create-invalidation
----------------------------------------
↓
AWSに反映
設定手順
(AWS側で設定)
1. AWSアカウント(環境)ごとにOIDC + IAMロールを準備
(GitHub側で設定)
2. GitHub Environmentsを3つ用意
(ソースコード側で設定)
3. ワークフローファイルの作成
1. AWSアカウント(環境)ごとにOIDC + IAMロールを準備
※各環境(dev/stg/prod)のAWSアカウントにS3/CloudFront/OACが作成済みであることを前提とします。
(以下、各AWSアカウントで1度のみおこないます)
① OIDCプロバイダを登録
AWSコンソール >> IAM >> アクセス管理 >> ID プロバイダ画面に遷移し、「プロバイダを追加」を押します。
① 「プロバイダのタイプ」は「OpenID Connect」を選択します。
② 「プロバイダの URL」はhttps://token.actions.githubusercontent.comを入力します。
③ 「対象者」はsts.amazonaws.comを入力します。
④ (必要であれば)タグを追加します。
⑤ 「プロバイダを追加」を押します。
何をしているのか
・AWS IAMに、「GitHub Actionsを信頼できる外部認証システム」として登録しています。
・GitHubが発行するOIDCトークン(JWT)をAWS STSが検証できるようにしています。
・これにより、GitHub ActionsはAWS Access Keyを持たずに一時認証でAssumeRoleできるようになります
② IAMポリシーを作成
AWSコンソール >> IAM >> アクセス管理 >> ポリシー画面に遷移し、「ポリシーの作成」を押します。

① 記述形式は「JSON」を選択します。
② 「ポリシーエディタ」に以下の内容をペーストします。
{
"Version": "2012-10-17",
"Statement": [
{
"Sid": "S3ListBucket",
"Effect": "Allow",
"Action": [
"s3:ListBucket",
"s3:GetBucketLocation"
],
"Resource": "arn:aws:s3:::YOUR_BUCKET_NAME"
},
{
"Sid": "S3ObjectRW",
"Effect": "Allow",
"Action": [
"s3:GetObject",
"s3:PutObject",
"s3:DeleteObject"
],
"Resource": "arn:aws:s3:::YOUR_BUCKET_NAME/*"
},
{
"Sid": "CloudFrontInvalidate",
"Effect": "Allow",
"Action": [
"cloudfront:CreateInvalidation",
"cloudfront:GetInvalidation",
"cloudfront:ListInvalidations"
],
"Resource": "arn:aws:cloudfront::*:distribution/YOUR_DISTRIBUTION_ID"
}
]
}
③ 「次へ」を押します。
① 「ポリシー名」を設定します。
<Project>-<Env>-<ResourceType>-<Purpose>のようにわかりやすくします。
② (必要であれば)タグを追加します。
③ 「ポリシーの作成」を押します。
【なぜ先にポリシーを作成するのか】
後述するIAMロールには、「インラインポリシー」をアタッチすることが可能です。
しかし、「インラインポリシー」は
・独立したIAMポリシーリソースとしては作成されない(そのロールの内部に埋め込まれるポリシー)
・他ロールへの再利用・単体管理・ARN参照はできない
・サイズ制限がある
・「ロール=誰が引き受けるか」と「権限定義(ポリシー)=何をしてよいか」は権限の責務を分離するのが主流
といった理由から、「小規模で拡張する予定がない」場合を除いて、「独立したポリシー」として作成することが推奨されています。
③ IAMロールを作成
AWSコンソール >> IAM >> アクセス管理 >> ロール画面に遷移し、「ロールを作成」を押します。
① 「信頼されたエンティティタイプ」は「カスタム信頼ポリシー」を選択します。
② 「カスタム信頼ポリシー」に以下の内容をペーストします。
{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Principal": {
"Federated": "arn:aws:iam::<YOUR_AWS_ACCOUNT_ID>:oidc-provider/token.actions.githubusercontent.com"
},
"Action": "sts:AssumeRoleWithWebIdentity",
"Condition": {
"StringEquals": {
"token.actions.githubusercontent.com:aud": "sts.amazonaws.com",
"token.actions.githubusercontent.com:sub": "repo:organization/repository:environment:dev"
}
}
}
]
}
Principal: 誰を信頼するかを定義
Federated: OIDC/SAMLの外部IDプロバイダを意味する
"Action": "sts:AssumeRoleWithWebIdentity": OIDC JWTを使ったAssumeRoleを許可
→通常のsts:AssumeRoleではなく、OIDC専用API。
Condition: 誰ならAssumeRoleできるか
aud: このJWTが誰向けかを定義→AWS STS用ならsts.amazonaws.comで固定
sub: どのGitHub workflowかを識別する→できる限り制限しないと、意図しないリポジトリ/ブランチからのアクセスを許可してしまうことになる
③ 「次へ」を押します。
① 先ほど作成したポリシーを検索し、選択します。
② 「次へ」を押します。
① 「ロール名」を設定します。
GitHubDeployRole-<Project>-<Env>など、わかりやすくします。
② (必要であれば)タグを追加します。
③ 「ロールを作成」を押します。
dev環境での設定が完了したら、同様の手順で'dev'を'stg/prod'に変えて設定します。
2. GitHub Environmentsを3つ用意
対象のリポジトリに、GitHub Actionsが参照する必要のある環境変数を「環境ごと」に設定していきます。
① 「Settings」タブを開きます。
② 「Environments」を開きます。
③ 「New environment」を押します。
① 「Name」にdevと入力します。
② 「Configure environment」を押します。
「Add environment variable」を押します。
今回登録する値はすべて公開しても問題ない値であるため「Secrets」は使用していませんが、「トークン/key」等の機密情報を登録する場合は「Secrets」を使用します。
また、全環境(dev/stg/prod)に共通する値は「Environment variables/secrets」ではなく、「Repository variables/secrets」に登録します。
以下の値を順番に登録していきます。
| 変数名 | 値(例) | 補足 |
|---|---|---|
| AWS_REGION | ap-northeast-1 | デプロイ先のリージョンを指定します。 Repository variablesに登録も可 |
| AWS_DEPLOY_ROLE_ARN | arn:aws:iam::123…:role/GitHubDeployRole-Project-Env | 先ほど作成したロール |
| S3_BUCKET_NAME | test-bucket-dev | デプロイ先のバケット |
| CLOUDFRONT_DISTRIBUTION_ID | E1AB2C | デプロイ先のディストリビューション |
| LIFF_ID | 2001234567-asdfghjk | ビルド時にNuxtへ渡す |
devが完了したら、stg/prodも同様に登録します。
環境ごとの保護ルール
今回はprod環境のみ、mainブランチ以外からのデプロイを禁止するルールを追加します。
prodのEnvironments設定画面へ移動します。
① 「Selected branches and tags」を選択します。
② mainブランチを追加します。
リポジトリの公開設定やプランによっては、EnvironmentのDeployment protection rulesから
・Required reviewers: 最低 2名
・Wait timer: 0〜5 分(誤承認の取消余地)
なども設定可能なようです。
3. ワークフローファイルの作成
プロジェクトのルートディレクトリに、以下の.github/workflows/deploy.ymlを作成します。
name: Deploy LIFF Site
run-name: Deploy LIFF Site 【${{ inputs.target_env }}】
on:
workflow_dispatch:
inputs:
target_env:
description: 'デプロイ先環境'
required: true
type: choice
options:
- dev
- stg
- prod
# 同一環境への並行デプロイを防止
# 実行中deployは止めず、後続deployはqueueする
concurrency:
group: deploy-${{ github.event.inputs.target_env }}
cancel-in-progress: false
permissions:
contents: read
id-token: write # OIDC AssumeRole に必須
jobs:
deploy:
name: Deploy to ${{ inputs.target_env }}
runs-on: ubuntu-latest
timeout-minutes: 15
environment:
name: ${{ inputs.target_env }}
steps:
- name: Validate deploy ref
env:
TARGET_ENV: ${{ inputs.target_env }}
DEPLOY_REF: ${{ github.ref_name }}
run: |
if [ "$TARGET_ENV" = "prod" ] && [ "$DEPLOY_REF" != "main" ]; then
echo "::error::prod deploys must use main (got: $DEPLOY_REF)"
exit 1
fi
- name: Checkout (${{ github.ref_name }})
uses: actions/checkout@v4
with:
# 履歴は不要なので浅く
fetch-depth: 1
- name: Setup pnpm
uses: pnpm/action-setup@v4
with:
# package.json の packageManager に従う
run_install: false
- name: Setup Node.js
uses: actions/setup-node@v4
with:
node-version: '22'
cache: 'pnpm'
- name: Install dependencies
run: pnpm install --frozen-lockfile
- name: Type check
run: pnpm typecheck
- name: Lint
run: pnpm lint
- name: Build (nuxt generate)
env:
NUXT_PUBLIC_LIFF_ID: ${{ vars.LIFF_ID }}
run: pnpm generate
- name: Configure AWS credentials (OIDC)
uses: aws-actions/configure-aws-credentials@v4
with:
role-to-assume: ${{ vars.AWS_DEPLOY_ROLE_ARN }}
aws-region: ${{ vars.AWS_REGION }}
role-session-name: gha-deploy-${{ inputs.target_env }}-${{ github.run_id }}
# S3 へ一括アップロード
# 全ファイルを短期キャッシュで統一(ハッシュ付きアセットも含む)
- name: Upload to S3
run: |
aws s3 sync ./.output/public/ s3://${{ vars.S3_BUCKET_NAME }}/ \
--delete \
--cache-control "no-cache, must-revalidate" \
--metadata-directive REPLACE \
--only-show-errors
# CloudFront 無効化(全パス /*)
- name: Create CloudFront invalidation
id: invalidate
run: |
INV_ID=$(aws cloudfront create-invalidation \
--distribution-id ${{ vars.CLOUDFRONT_DISTRIBUTION_ID }} \
--paths "/*" \
--query 'Invalidation.Id' \
--output text)
echo "invalidation_id=$INV_ID" >> $GITHUB_OUTPUT
echo "Invalidation: $INV_ID"
- name: Wait for invalidation to complete
run: |
aws cloudfront wait invalidation-completed \
--distribution-id ${{ vars.CLOUDFRONT_DISTRIBUTION_ID }} \
--id ${{ steps.invalidate.outputs.invalidation_id }}
- name: Summary
env:
DEPLOY_ENV: ${{ inputs.target_env }}
run: |
printf '## ✅ Deploy completed (`%s`)\n' "$DEPLOY_ENV" >> "$GITHUB_STEP_SUMMARY"
environment: ${{inputs.target_env}}: この設定によってGitHub Environmentsの「承認フロー/ブランチ保護/Variables」が使用できるようになります。
id-token: write: AssumeRoleWithWebIdentityに必要なOIDCトークン発行権限です。job単位で付与し、他jobには伝播させないようにします。
role-session-name: CloudTrailにgha-deploy-prod-12345のようなセッション名が残り、監査時に「誰の・どのrun」かを即特定可能になります。
aws s3 sync: フロントエンドのコンテンツを更新します。(大規模なアプリの場合、長期キャッシュと短期キャッシュで分けることも可能です)
--metadata-directive REPLACE: Cache-Controlを確実に上書きします。
aws cloudfront wait: 反映待ちまで含めてジョブ成功とします。
他、気になる箇所があればAIに投げて質問してみてください。
実際にデプロイしてみる
mainブランチからtest-devブランチを作成し、トップページにアクセスしたら「This is dev」と表示されるようにコードを実装後リモートにブランチをpushしました。
① 対象のリポジトリの「Action」タブを開きます。
② 「Deploy LIFF Site」を押します。
① 「Run workflow」を押します。
② デプロイしたいブランチを選択します。
③ デプロイ先の環境を選択します。
④ 「Run workflow」を押します。
デプロイが完了しました。
アプリにアクセスしたら無事に反映されていました!
このtest-devブランチを「prod」環境にデプロイしようとすると、ちゃんとエラーも出ます。
今回は以上になります。



















