はじめに
アプリのコードが書けないインフラエンジニアでも、AIと組めば半日でWebアプリを公開し、自動デプロイまで作れました。費用は月0円です。
私はAWSの構築経験はありますが、ReactやTypeScriptは書けません。ある朝、ブラウザで使えるポモドーロタイマーを見かけ、「同じようなものをAWSでなるべく安く作れないか」とClaudeに相談したのが始まりです。
この記事では、構成を決めるところから、S3とCloudFrontでの公開、GitHub Actionsによる自動デプロイ、そして一番ハマったOIDCのエラーまで、実際にやった順に書きます。
設定値やポリシーのJSON、ワークフローの全文など、再現するための具体的な手順は手順書にまとめました。
【手順書】S3+CloudFront(OAC)+GitHub Actions(OIDC)で静的サイトのCI/CDを構築する
構成を決める:サーバーを持たない
結論は「S3にファイルを置き、CloudFrontで配信するだけ」の静的サイトです。ポモドーロタイマーは、時間を数える・音を鳴らす・設定を覚えるがすべてブラウザ内で完結するため、サーバーもデータベースも要りません。
設定や今日の集中回数はブラウザのlocalStorageに保存します。ログインや複数端末の同期が欲しくなったら、Cognito、API Gateway、Lambda、DynamoDBのサーバーレス構成を後から足す、という二段構えにしました。
| 役割 | 使ったもの |
|---|---|
| 画面 | React + TypeScript + Vite |
| ファイル置き場 | Amazon S3(非公開のまま) |
| 配信とHTTPS | Amazon CloudFront(OACでS3に接続) |
| ソース管理 | GitHub |
| 自動デプロイ | GitHub Actions(OIDCでIAMロールを引き受け) |
EC2とRDSで組むと最低でも月数千円かかりますが、この構成ならCloudFrontの無料プランに収まります。
アプリはAIに作ってもらう
「コードは書けない」と伝えると、Claudeがその場でアプリを作り、ビルド済みの成果物(dist/)とソース一式をzipで渡してくれました。ビルド後に画面を実際に起動し、スクリーンショットで崩れがないかまで確認済みでした。
最初の版は25分集中と5分休憩を回す、シンプルなタイマーです。公開後に気になった点を伝えると、すぐ直った版が返ってきました。
- PCで左右の余白が広すぎる → 2カラムにして、左に大きなタイマー、右に「今日のサイクル」「今日の記録」「設定」を並べた
- 集中・休憩・長い休憩のタブが分かりにくい → タブをやめ、集中→休憩→…→長い休憩の流れを縦に並べ、今どこにいて次が何かを表示
- タイマーの下が空いている → 通知音を5種類(ベル、チャイム、電子音、マリンバ、やさしい鐘)と音量スライダーを追加
通知音は音声ファイルではなく、ブラウザのWeb Audio APIで合成しています。そのためファイルが増えず、S3の容量も料金も変わりません。
S3とCloudFrontで公開する
S3は「パブリックアクセスをすべてブロック」をオンのまま作り、CloudFrontのOAC経由でだけ読めるようにしました。作業は30分ほどです。
- 東京リージョンにバケットを作る。静的ウェブサイトホスティングは有効にしない
-
dist/の中身(index.html、favicon.svg、assets/)をバケット直下にアップロードする - CloudFrontのディストリビューションを作る。オリジンにバケットを選び、オリジンパスは空欄、オリジンアクセスはOAC
- デフォルトルートオブジェクトに
index.htmlを設定する -
https://xxxx.cloudfront.netを開いて表示を確認する
新しい作成ウィザードでは料金プランを選べ、無料プラン(月0ドル)にはWAFの基本保護も含まれていました。「Grant CloudFront access to origin」をYesにすると、CloudFrontのサービスプリンシパルにこのディストリビューションからの s3:GetObject だけを許すバケットポリシーが自動で書き込まれます。手で貼る必要はありませんでした。
ハマりかけたのは手順4です。ウィザードに項目がなく未設定のままで、作成後に設定画面から入れ直しました。ここが空だと、トップURLでAccessDeniedになります。
GitHub Actionsで自動デプロイにする
main にpushすると、1〜2分でビルドからS3同期、CloudFrontの無効化まで自動で走るようにしました。アクセスキーは発行せず、OIDCで一時的な認証情報をもらいます。
- GitHubにリポジトリを作り、ソースをpushする
- IAMで、OIDCプロバイダー
token.actions.githubusercontent.com(対象者sts.amazonaws.com)を作る - デプロイ用のIAMロールを作る。信頼ポリシーでリポジトリと
mainブランチに限定し、権限はS3のListBucket、PutObject、DeleteObjectと、CloudFrontのCreateInvalidationだけにする - GitHubのRepository variablesに、
AWS_ROLE_ARN、S3_BUCKET、CLOUDFRONT_DISTRIBUTION_IDを登録する -
.github/workflows/deploy.ymlを置いて実行する
小さな落とし穴が2つありました。
-
ブラウザからアップロードすると
.githubが抜ける。 隠しフォルダは除外されるので、GitHubの画面で「Create new file」から作りました -
Environment variablesに入れても読まれない。 ワークフローで
environment:を指定していないので、Repository variablesに登録します
料金は、Publicリポジトリなら無料、Privateでも月2,000分の無料枠があります。1回1〜2分なので、まず超えません。
最大のハマりどころ:subの形式が違っていた
初回のデプロイは「Configure AWS credentials」で失敗しました。原因は、GitHubから届く sub にユーザーIDとリポジトリIDが含まれていたことです。
Error: Could not assume role with OIDC: Not authorized to perform sts:AssumeRoleWithWebIdentity
ロールのARN、audience、ワークフローの id-token: write は、どれも正しく設定されていました。推測で直すのをやめ、CloudTrailのイベント履歴で AssumeRoleWithWebIdentity の失敗イベントを開くと、userIdentity.userName に実際に届いた値がありました。
信頼ポリシーの条件: repo:<ユーザー名>/pomodoro:ref:refs/heads/main
実際に届いた sub : repo:<ユーザー名>@<ユーザーID>/pomodoro@<リポジトリID>:ref:refs/heads/main
IAMコンソールのロール作成ウィザードが作るのは、名前だけの従来形式です。信頼ポリシーの sub を届いた値にそろえ、StringEquals の完全一致にしたら通りました。
数字のIDが入る形式は、むしろ安全です。アカウント名を変えたあとに第三者が同じ名前でリポジトリを作っても、IDが違うのでロールを引き受けられません。
教訓は「権限エラーは、相手側が実際に送ってきた値を見る」です。CloudTrailは、拒否されたリクエストの中身まで残してくれていました。
ビルドとは何だったのか
S3には index.html、favicon.svg、assets/ しかないのに、App.tsx の変更が反映されました。S3に置いているのはソースではなく、ソースを変換した成果物だからです。
ブラウザは .tsx をそのまま実行できません。そこで npm run build が、src/ のコードとReact本体を普通のJavaScriptに変換して1つにまとめ、assets/index-xxxx.js を作ります。中身が変わるとファイル名のハッシュも変わるので、index.html だけをキャッシュさせなければ古い版は残りません。
ビルド用のサーバーも持っていません。runs-on: ubuntu-latest の1行で、GitHubが毎回まっさらなVMを用意し、終わったら捨ててくれます。Javaのソースから .jar を作って配置するのと同じで、ソースはGitHub、成果物はS3という分担です。
最初はClaudeの作業環境がこのランナーの役割をしていて、成果物をzipで受け取り、手作業でS3に上げていました。今はpushするだけです。
かかった時間と費用
最初の相談から自動デプロイの動作確認まで、約1時間半でした。毎月の費用は0円です。
| 項目 | 月額 |
|---|---|
| S3(数百KBのファイル) | ほぼ0円 |
| CloudFront(無料プラン、WAFの基本保護込み) | 0円 |
| GitHub Actions(1回1〜2分) | 0円(無料枠内) |
| IAM、OIDCプロバイダー、CloudTrailのイベント履歴 | 0円 |
まとめ
コードが書けなくても、インフラの知識があれば「AIにアプリを作らせ、自分は基盤を作る」という分担で十分戦えます。権限、キャッシュ、ログといったインフラ側の勘所は、AIと組んでもそのまま武器になりました。
次は、独自ドメイン(Route 53とACM)、作業名の入力と週間グラフ、PWA化に挑戦する予定です。
同じ構成を自分で作りたい方は、手順書をどうぞ。