0
3

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

【S3 + CloudFront】GitHub Actionsで更新を自動化する

0
Posted at

はじめに

今回は、前回の続きとなります。
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_dispatchtarget_envrefを選択(今回は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 プロバイダ画面に遷移し、「プロバイダを追加」を押します。

SCR-20260515-oano.png

① 「プロバイダのタイプ」は「OpenID Connect」を選択します。
② 「プロバイダの URL」はhttps://token.actions.githubusercontent.comを入力します。
③ 「対象者」はsts.amazonaws.comを入力します。
④ (必要であれば)タグを追加します。
⑤ 「プロバイダを追加」を押します。

SCR-20260515-odeo.png

何をしているのか

・AWS IAMに、「GitHub Actionsを信頼できる外部認証システム」として登録しています。
・GitHubが発行するOIDCトークン(JWT)をAWS STSが検証できるようにしています。
・これにより、GitHub ActionsはAWS Access Keyを持たずに一時認証でAssumeRoleできるようになります

② IAMポリシーを作成

AWSコンソール >> IAM >> アクセス管理 >> ポリシー画面に遷移し、「ポリシーの作成」を押します。
SCR-20260515-osak.png

① 記述形式は「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"
    }
  ]
}

③ 「次へ」を押します。

SCR-20260515-otah.png

① 「ポリシー名」を設定します。
<Project>-<Env>-<ResourceType>-<Purpose>のようにわかりやすくします。
② (必要であれば)タグを追加します。
③ 「ポリシーの作成」を押します。

SCR-20260519-kdfd.png

【なぜ先にポリシーを作成するのか】
後述するIAMロールには、「インラインポリシー」をアタッチすることが可能です。
しかし、「インラインポリシー」は
・独立したIAMポリシーリソースとしては作成されない(そのロールの内部に埋め込まれるポリシー)
・他ロールへの再利用・単体管理・ARN参照はできない
・サイズ制限がある
・「ロール=誰が引き受けるか」と「権限定義(ポリシー)=何をしてよいか」は権限の責務を分離するのが主流
といった理由から、「小規模で拡張する予定がない」場合を除いて、「独立したポリシー」として作成することが推奨されています。

③ IAMロールを作成

AWSコンソール >> IAM >> アクセス管理 >> ロール画面に遷移し、「ロールを作成」を押します。

SCR-20260515-ogtb.png

① 「信頼されたエンティティタイプ」は「カスタム信頼ポリシー」を選択します。
② 「カスタム信頼ポリシー」に以下の内容をペーストします。

{
  "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かを識別する→できる限り制限しないと、意図しないリポジトリ/ブランチからのアクセスを許可してしまうことになる

③ 「次へ」を押します。

SCR-20260515-onwh.png

① 先ほど作成したポリシーを検索し、選択します。
② 「次へ」を押します。

SCR-20260519-knym.png

① 「ロール名」を設定します。
GitHubDeployRole-<Project>-<Env>など、わかりやすくします。
② (必要であれば)タグを追加します。
③ 「ロールを作成」を押します。

SCR-20260519-kplh.png

dev環境での設定が完了したら、同様の手順で'dev'を'stg/prod'に変えて設定します。

2. GitHub Environmentsを3つ用意

対象のリポジトリに、GitHub Actionsが参照する必要のある環境変数を「環境ごと」に設定していきます。

① 「Settings」タブを開きます。
② 「Environments」を開きます。
③ 「New environment」を押します。

SCR-20260519-kxne.png

① 「Name」にdevと入力します。
② 「Configure environment」を押します。

SCR-20260519-kyew.png

「Add environment variable」を押します。

今回登録する値はすべて公開しても問題ない値であるため「Secrets」は使用していませんが、「トークン/key」等の機密情報を登録する場合は「Secrets」を使用します。
また、全環境(dev/stg/prod)に共通する値は「Environment variables/secrets」ではなく、「Repository variables/secrets」に登録します。

SCR-20260519-kyvf.png

以下の値を順番に登録していきます。

変数名 値(例) 補足
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へ渡す

SCR-20260519-lebw.png

devが完了したら、stg/prodも同様に登録します。

SCR-20260519-lgon.png

環境ごとの保護ルール

今回はprod環境のみ、mainブランチ以外からのデプロイを禁止するルールを追加します。

prodのEnvironments設定画面へ移動します。

SCR-20260519-lied.png

① 「Selected branches and tags」を選択します。
② mainブランチを追加します。

SCR-20260519-lile.png

リポジトリの公開設定やプランによっては、EnvironmentのDeployment protection rulesから
・Required reviewers: 最低 2名
・Wait timer: 0〜5 分(誤承認の取消余地)
なども設定可能なようです。

3. ワークフローファイルの作成

プロジェクトのルートディレクトリに、以下の.github/workflows/deploy.ymlを作成します。

.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」を押します。

SCR-20260519-ogpd.png

① 「Run workflow」を押します。
② デプロイしたいブランチを選択します。
③ デプロイ先の環境を選択します。
④ 「Run workflow」を押します。

SCR-20260519-ojcn.png

デプロイが完了しました。

SCR-20260519-onsu.png

アプリにアクセスしたら無事に反映されていました!

SCR-20260519-ooad.png

このtest-devブランチを「prod」環境にデプロイしようとすると、ちゃんとエラーも出ます。

SCR-20260519-oqbp.png

今回は以上になります。

0
3
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
3

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?