前回の記事では、S3とCloudFrontで公開したAstroブログに、Route 53とACMを使って独自ドメインとHTTPSを設定する流れをまとめました。 まだそこまで進んでいない場合は、先にAstroブログに独自ドメインを設定してみる!Route 53・ACM・CloudFrontでHTTPS化から読むとつながりやすいです。
その前段として、Astroでブログを作ってみる!記事を書くまでの流れを初心者向けに解説と、AstroブログをAWSで公開してみる!S3とCloudFrontで静的サイトを配信する流れもあります。
今回はその続きです。
ローカルで毎回 npm run build して、手作業でS3へアップロードするのはだんだん面倒になってきます。
そこで、GitHub ActionsからAWSへデプロイする流れを整理します。
ただし、最初に大事な注意です。
このブログ「あれこれびより」の本番デプロイは、現時点では push しただけで勝手に動く完全自動デプロイではありません。
GitHubへpushしたあと、GitHub ActionsのWorkflowを手動実行する形にしています。
今回やること
今回のゴールは、GitHub ActionsからAstroブログをビルドして、AWSのS3とCloudFrontへ反映する流れを理解することです。
ざっくり書くと、こういう流れです。
ローカルで記事やコードを修正
↓
Git commit / push
↓
GitHub上のコードが更新される
↓
GitHub Actionsの本番デプロイWorkflowを手動実行
↓
npm ci
↓
記事・画像チェック
↓
npm run build
↓
OIDCでAWSのIAM Roleを一時的に引き受ける
↓
dist/ をS3へ同期
↓
CloudFrontのキャッシュを無効化
↓
本番サイトへ反映
今回のポイントは、AWSの長期Access KeyをGitHub Secretsへ保存しないことです。 GitHub Actions OIDCを使って、GitHub ActionsがAWSのデプロイ用IAM Roleを一時的に使う形にしています。
前回までの構成
前回までで、ブログの公開構成はだいたいここまで来ています。
Astro
↓ npm run build
dist/
↓
Amazon S3
↓
Amazon CloudFront
↓
Route 53
↓
https://example.com
Astroで生成した dist/ をS3へ置き、読者はCloudFront経由でアクセスします。
独自ドメインはRoute 53でCloudFrontへ向け、HTTPS用の証明書はACMで用意する構成でした。
ここまでできるとブログとしては公開できます。 ただ、記事を直すたびに手動でアップロードする運用だと、だんだんつらくなります。
毎回S3へ手動アップロードするのは面倒
記事を1本追加するたびに、手元でビルドして、S3へアップロードして、CloudFrontのキャッシュを更新する。 最初の1回ならまだよいですが、ブログを続けるならこれは地味に面倒です。
しかも、手作業だとミスもしやすいです。
npm run buildを忘れる- 古い
dist/をアップロードしてしまう - 消した記事のファイルがS3に残る
- CloudFrontのキャッシュ更新を忘れる
- 本番に必要な環境変数を入れ忘れる
こういう作業は、GitHub Actionsに任せたほうが楽です。 人間は記事を書くことに集中して、決まった手順はWorkflowにやってもらう感じっすね。
GitHub Actionsって何?
GitHub Actionsは、GitHub上で処理を自動実行できる仕組みです。
たとえば、このブログでは次のような作業をWorkflowに書いています。
- リポジトリをcheckoutする
- Node.jsをセットアップする
npm ciで依存関係を入れる- 記事と画像をチェックする
- Astroをビルドする
- AWSへ認証する
- S3へ
dist/を同期する - CloudFrontのキャッシュを無効化する
このような一連の作業を、.github/workflows/deploy.yml に書いています。
今回作るデプロイの流れ
GitHub ActionsからAWSへデプロイする流れは、かなりざっくり言うとこうです。
GitHub Actions
│
│ OIDC
▼
AWS IAM / STS
│
│ AssumeRole
▼
デプロイ用IAM Role
│
├── S3
│
└── CloudFront
ここで出てくる言葉がちょっと多いですね。 でも、最初はこう考えると分かりやすいです。
GitHub ActionsがAWSへ「このWorkflowは、このリポジトリの、この本番デプロイです」と示します。 AWS側はTrust Policyで条件を確認し、問題なければデプロイ用IAM Roleを一時的に使わせます。 その一時的な権限で、S3同期やCloudFront Invalidationを実行する、という流れです。
AWSへどうやってログインする?
ここで初心者が気になりやすいのが、「誰がAWSへログインしているの?」というところです。
今回の構成では、人間のIAMユーザーでログインしているわけではありません。
また、AWS_ACCESS_KEY_ID や AWS_SECRET_ACCESS_KEY をGitHubへ保存しているわけでもありません。
GitHub ActionsがOIDCを使って、AWSのデプロイ用IAM Roleを一時的に引き受けています。
このときAWS側では、STSという仕組みを使って一時的な認証情報が発行されます。 STSはSecurity Token Serviceの略で、AWSで一時的な認証情報を扱うサービスです。 ただ、この記事では深追いしすぎず、「短時間だけ使えるAWSの権限を受け取る仕組み」くらいで大丈夫です。
Access KeyをGitHubへ保存しない
GitHub ActionsからAWSを操作する記事では、次のようなSecretsを登録する例もよく見かけます。
AWS_ACCESS_KEY_ID
AWS_SECRET_ACCESS_KEY
これは長期間使えるAccess KeyをGitHub側へ保存する方式です。 もちろん、適切に管理すれば使える構成ではあります。 ただ、Keyが長期間有効なぶん、漏れたときの影響も考える必要があります。
今回の構成では、この長期Access Key方式は使っていません。
従来の一例
GitHub Secrets
↓
長期間有効なAccess Key
↓
AWS
今回の構成はこうです。
GitHub Actions
↓
OIDC
↓
AWS STS
↓
一時的な認証情報
↓
AWS
長期Access KeyをGitHub側へ保存しなくてよいのが大きなメリットです。 ただし、OIDCにしたら何でも安全になる、というわけではありません。 IAM Roleの権限やTrust Policyをきちんと絞ることが大事っすね。
GitHub Actions OIDCって何?
OIDCはOpenID Connectの略です。 GitHub Actionsでは、OIDCを使って外部クラウドへ認証できます。
GitHub公式ドキュメントでも、AWS向けOIDCでは長期のAWS認証情報をGitHub Secretsへ保存せずにAWSへアクセスできる、と説明されています。 参考: Configuring OpenID Connect in Amazon Web Services
AWS側では、GitHubのOIDC Providerとして次のURLを使います。
https://token.actions.githubusercontent.com
Audienceは、公式の aws-actions/configure-aws-credentials を使う場合、通常は次の値です。
sts.amazonaws.com
このあたりは仕様に関わる部分なので、実際に作るときはAWSとGitHubの公式ドキュメントを確認するのが安全です。
AWS側にデプロイ用IAM Roleを用意する
GitHub ActionsからAWSを操作するには、AWS側にデプロイ用IAM Roleを用意します。
記事ではダミー名として、次のようなRole名で考えます。
arekore-blog-github-deploy-role
このRoleには、ブログのデプロイに必要な権限だけを付けます。 今回の代表例はこのあたりです。
- 対象S3 Bucketを一覧する
- 対象S3 Bucketへファイルを配置する
- 対象S3 Bucketから不要ファイルを削除する
- 対象CloudFront Distributionのキャッシュを無効化する
つまり、デプロイ用Roleだからといって AdministratorAccess のような強い権限を付ける必要はありません。
ブログのデプロイに必要な範囲へ絞るのが基本です。
Trust PolicyでGitHubからの利用を制限する
IAM Roleには、権限ポリシーとは別にTrust Policyがあります。
Trust Policyは、ざっくり言うと「このRoleを誰が使ってよいか」を決める設定です。 GitHub Actions OIDCの場合は、GitHubのOIDC Providerから来たリクエストを条件付きで許可します。
AWS公式ドキュメントでも、OIDC Provider用のRoleでは sts:AssumeRoleWithWebIdentity を使う説明があります。
参考: Create a role for OpenID Connect federation
記事用にかなり簡略化した例を書くと、こんな感じです。
実際のAWS Account IDやRepository名は必ず自分の環境に合わせます。
次の repo:example-user/example-blog:environment:production は、従来形式の sub を使っているRepository向けの例です。
{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Principal": {
"Federated": "arn:aws:iam::123456789012: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:example-user/example-blog:environment:production"
}
}
}
]
}
GitHub Environmentを使うWorkflowでは、sub に environment:production のような形が出てきます。
GitHub公式ドキュメントでも、Environmentを使う場合の sub はEnvironment名を参照する形になる、と説明されています。
さらに、GitHub公式のOIDC referenceでは、2026年7月15日以降に作成されたRepositoryは、immutable subject claims形式がデフォルトになると説明されています。
この形式では、sub の中にowner IDとrepository IDが含まれます。
一方で、それ以前に作成されたRepositoryは、opt-inなどをしていなければ従来形式のままです。
また、2026年7月15日以降にRepositoryをrenameしたりtransferしたりした場合も、immutable形式へ移行する場合があります。
GitHub Environmentを使う場合は、従来形式でもimmutable形式でも、Environment名がsubjectへ反映されます。
なので、実際に作るときは、自分のRepositoryが発行する sub の形式とAWS側のTrust Policyを一致させる必要があります。
初心者向けに言うと、「GitHubなら何でもOK」ではなく、「このRepositoryの、このEnvironmentからだけOK」に絞るのが大事、ということっすね。
GitHub Environmentを用意する
このブログの本番デプロイWorkflowでは、GitHub Environmentとして production を使っています。
Workflowには次のように書かれています。
environment: production
Environmentを使うと、本番デプロイ用のVariablesやSecretsを分けて管理できます。 さらに、GitHub側で本番EnvironmentへデプロイできるBranchを制限したり、承認ルールを置いたりできます。
このブログのWorkflowでは、次のような値をEnvironment Variablesとして参照しています。
AWS_REGION
AWS_ROLE_ARN
S3_BUCKET
CLOUDFRONT_DISTRIBUTION_ID
PUBLIC_SITE_URL
PUBLIC_GA4_ID
PUBLIC_VIEWS_API_URL
ここで注意したいのは、Variablesに入れてよい値とSecretsにすべき値を分けることです。
たとえば、公開サイトURLやAWSリージョンのように知られても困りにくい値はVariablesで扱いやすいです。 一方で、API SecretやTokenのような機密情報はSecretsに入れるべきです。
また、Astroの PUBLIC_* はクライアントへ公開される前提の値です。
PUBLIC_* にAWS Secret Access Keyのような秘密情報を入れるのは絶対に避けます。
GitHub Actions Workflowを作る
このブログの本番デプロイWorkflowは、.github/workflows/deploy.yml にあります。
起動条件は workflow_dispatch です。
on:
workflow_dispatch:
つまり、push時に自動実行されるのではなく、GitHub Actions画面から手動実行します。 この運用だと、pushしただけでは本番反映されません。
また、Workflowには同時実行を抑える設定もあります。
concurrency:
group: deploy-production
cancel-in-progress: true
本番デプロイが重なって動くとややこしいので、同じグループの実行を制御している形ですね。
permissionsでOIDC Tokenを使えるようにする
OIDCを使うには、Workflowに次のpermissionsが必要です。
permissions:
contents: read
id-token: write
contents: read は、Repositoryをcheckoutするために使います。
id-token: write は、GitHub ActionsがOIDC Tokenを取得するために必要です。
GitHub公式ドキュメントでも、OIDC ProviderがJWTを作るために id-token: write が必要だと説明されています。
ここでいう write は、GitHubのコードを書き換える権限という意味ではありません。
OIDC Tokenをリクエストできるようにするための権限です。
名前だけ見るとちょっと怖く見えますが、役割を分けて考えると分かりやすいです。
Astroをbuildする
このブログのWorkflowでは、まずRepositoryをcheckoutし、Node.jsをセットアップします。
- name: Checkout
uses: actions/checkout@v4
- name: Setup Node.js
uses: actions/setup-node@v4
with:
node-version: 22
cache: npm
Node.jsは 22 を使っています。
cache: npm も設定しているので、npmのキャッシュを使って依存関係のインストールを速くしやすくしています。
依存関係のインストールは npm ci です。
- name: Install dependencies
run: npm ci
npm ci は package-lock.json を前提に、CI向けに依存関係をきれいに入れるコマンドです。
package.json と package-lock.json がずれていると失敗することがあります。
本番デプロイ前に気づけるので、CIでは npm install より npm ci のほうが向いています。
そのあと、記事と画像のチェックを実行しています。
- name: Check content
run: npm run check:content
- name: Check images
run: npm run check:images
このブログ独自のチェックですね。 Frontmatterの不足、views管理、画像参照切れなどを見ています。
最後にAstroをビルドします。
- name: Build
run: npm run build
package.json では、npm run build は次のようになっています。
{
"build": "astro check && astro build"
}
つまり、Astroのチェックをしてから静的ファイルを生成します。
生成された dist/ が、S3へ同期する対象です。
OIDCでAWSのRoleを引き受ける
ビルドが終わったら、AWSへ認証します。
このブログでは、aws-actions/configure-aws-credentials@v4 を使っています。
- name: Configure AWS credentials
uses: aws-actions/configure-aws-credentials@v4
with:
role-to-assume: ${{ env.AWS_ROLE_ARN }}
aws-region: ${{ env.AWS_REGION }}
audience: sts.amazonaws.com
role-session-name: GitHubActions-${{ github.run_id }}
このActionは、GitHub ActionsのOIDC Tokenを使ってAWSのRoleを引き受け、以降の aws コマンドで使える認証情報を設定してくれます。
公式のActionのREADMEでも、OIDCを使ったRole引き受けの設定例が説明されています。
参考: aws-actions/configure-aws-credentials
この記事では、あれこれびよりで現在使用している v4 を掲載しています。
新しく導入する場合は、公式READMEで現在の推奨バージョンや最新バージョンを確認してください。
ここで実際に使うRole ARNは、GitHub EnvironmentのVariablesから渡しています。 記事本文では実値を書かず、必要なら次のようなダミーで説明します。
arn:aws:iam::123456789012:role/arekore-blog-github-deploy-role
distをS3へ同期する
AWS認証ができたら、dist/ をS3へ同期します。
このブログのWorkflowでは、通常ファイルと _astro 配下でCache-Controlを分けています。
考え方としては、次のようなコマンドです。
aws s3 sync dist "s3://example-blog-bucket" \
--delete \
--exclude "_astro/*" \
--cache-control "public,max-age=0,must-revalidate"
if [ -d "dist/_astro" ]; then
aws s3 sync dist/_astro "s3://example-blog-bucket/_astro" \
--delete \
--cache-control "public,max-age=31536000,immutable"
fi
--delete は、dist/ から消えたファイルをS3側からも削除するための指定です。
記事を削除したときに、S3に古いHTMLが残り続けるのを防ぎやすくなります。
また、_astro 配下はAstroが生成するCSSやJavaScriptなどのアセットが入ります。
ファイル名にハッシュが付くものは、長くキャッシュしやすいです。
一方でHTMLなどは更新されやすいので、短めに再確認する設定にしています。
ここは地味ですが、かなり大事なところです。 キャッシュ設定を雑にすると、「デプロイしたのに古い表示が残る」みたいなことが起きやすいっすね。
CloudFrontのキャッシュを更新する
S3へ新しいファイルを置いても、CloudFrontに古いキャッシュが残っている場合があります。
そのため、このブログではデプロイの最後にCloudFront Invalidationを実行しています。
aws cloudfront create-invalidation \
--distribution-id "EXXXXXXXXXXXXX" \
--paths "/*"
Distribution IDは環境ごとに違うので、記事ではダミー値にしています。
初心者向けに言うと、CloudFront Invalidationは「CloudFrontに残っている古いキャッシュを更新対象にする」作業です。 S3へ最新ファイルを置くだけではなく、CloudFront側も更新されるようにしているわけですね。
Workflowを実行してみる
このブログのWorkflowは手動実行です。 なので、流れとしてはこうです。
ローカルで記事やコードを修正
↓
git commit
↓
git push
↓
GitHub ActionsのDeploy Workflowを手動実行
ここで、Workflowには次の条件も入っています。
if: github.ref == 'refs/heads/main'
つまり、mainブランチ以外では本番デプロイJobが動かないようにしています。
さらにGitHub Environment production 側でも、mainだけ本番デプロイできるように制限しておくと安心です。
IAM側は「GitHub Environment productionからのOIDC」を見て、GitHub側は「mainだけproductionへデプロイできる」ようにする。 このように、AWS側とGitHub側で役割を分けて守るのがよいです。
デプロイできたか確認する
Workflowが成功したら、本番サイトを確認します。
見るポイントはこのあたりです。
- トップページが更新されている
- 新しい記事ページが開ける
- CSSや画像が表示されている
- 404にならない
- 古い記事を消した場合、古いURLが残っていない
- CloudFront経由で最新内容が見えている
もし反映が遅れて見える場合は、CloudFrontのキャッシュやブラウザキャッシュの影響もあります。
ただ、このブログのWorkflowでは最後に create-invalidation --paths "/*" を実行しているので、少なくともCloudFront側へ更新対象として伝える処理は入っています。
うまくいかないときに見るところ
GitHub Actions + AWS OIDCで詰まるときは、だいたい見る場所が決まっています。
まず、npm ci で落ちている場合です。
この場合はAWS以前の問題で、package.json と package-lock.json の不一致や依存関係の問題を見ます。
次に、npm run check:content や npm run check:images で落ちている場合です。
記事Frontmatter、views管理、画像ファイルの置き忘れなどを確認します。
AWS認証で落ちる場合は、次のあたりを見ます。
permissionsにid-token: writeがあるかaws-actions/configure-aws-credentialsの設定が正しいかAWS_ROLE_ARNがEnvironment Variablesに設定されているか- IAM RoleのTrust PolicyがWorkflowの条件と合っているか
- GitHub Environment
productionをWorkflowで使っているか - mainブランチから実行しているか
S3同期で落ちる場合は、S3 Bucket名やIAM RoleのS3権限を見ます。
CloudFront Invalidationで落ちる場合は、Distribution IDや cloudfront:CreateInvalidation の権限を確認します。
エラーが出たら、まず「どのStepで落ちたか」を見るのが大事です。 最初から全部疑うとしんどいので、落ちたStepごとに切り分ける感じっすね。
OIDCにしたら何がよかった?
OIDCにした一番のメリットは、長期Access KeyをGitHubへ保存しなくてよいところです。
今回の構成では、GitHub ActionsがAWSへ直接パスワードのようなKeyを持っているわけではありません。 GitHub ActionsがOIDC Tokenを取得し、AWSが条件を確認し、問題なければ一時的な認証情報を発行します。
よかったところをまとめると、こんな感じです。
- 長期Access KeyをGitHubへ保存しなくてよい
- デプロイ専用IAM Roleに権限を絞れる
- Trust Policyで利用元を制限できる
- GitHub Environmentと組み合わせて本番デプロイを管理しやすい
- Workflow側のAWS認証が分かりやすくなる
一方で、OIDCだから絶対安全というわけではありません。 Trust Policyが広すぎたり、IAM Roleに強すぎる権限を付けたりすると危ないです。
なので、OIDCは「Access Keyを置かないための便利な仕組み」ですが、IAM設計はちゃんと考える必要があります。
まとめ
今回は、GitHub ActionsとOIDCを使って、AstroブログをAWSへデプロイする流れを整理しました。
このブログでは、現時点で次のような運用にしています。
workflow_dispatchで本番デプロイWorkflowを手動実行する- mainブランチのときだけJobを実行する
- GitHub Environment
productionを使う - Node.js 22でビルドする
npm ciで依存関係を入れるcheck:contentとcheck:imagesを実行するnpm run buildでdist/を作る- OIDCでAWSのデプロイ用IAM Roleを引き受ける
dist/をS3へ同期する_astro配下は長期キャッシュ向けに分けて同期する- CloudFront Invalidationでキャッシュ更新を行う
これで、手元から毎回S3へアップロードしなくても、GitHub ActionsからAWSへ安全寄りにデプロイできるようになります。
ブログ運用では、記事を書くこと以外にも、ビルド、アップロード、キャッシュ更新など細かい作業が出てきます。 そこをWorkflowに任せられると、かなり気が楽になりますね。
今後は、閲覧数カウンターやLambda + DynamoDB、AWS監視まわりも少しずつ整理していきたいです。
ではでは、おつかれっした~。