手動アップロードに挫折した私が、GitHub Actions に仕事を任せるまで ― deploy.yml 日本語訳付き

記事を直すたびに、ビルドしてS3にアップロードしてキャッシュを消す。この手作業が面倒すぎて、前に一度ブログを挫折しました。今回はGitHub Actionsに任せます。設定ファイル deploy.yml を、上から順に日本語に訳しました。

目次
  1. 実は、一度挫折している
  2. 自動化してみたい
  3. GitHub Actions とは?
  4. おまけ:AWSで運用しているのに、デプロイはGitになる
  5. 動かすには、決まった場所にファイルを1枚置く
  6. ymlって、Dockerでも見たやつ
  7. まず、deploy.yml の全容
  8. deploy.yml を日本語に訳す
  9. ① この自動化の名前
  10. ② いつ動くか
  11. ③ この作業に渡す権限
  12. ④ 使い回す値に名前を付ける
  13. ⑤ どのマシンで動かすか
  14. ⑥ 記事を取ってきて、Nodeを用意する
  15. ⑦ ビルドする
  16. ⑧ AWSに入る鍵を借りる
  17. ⑨ S3にアップロードする(2回に分ける)
  18. ⑩ 古いキャッシュを消す
  19. アクセスキーを置かなくていい(OIDC)
  20. できあがったもの

git push すると、1分後にはブログが更新されている。

今はその状態になりました。ただ、ここに来るまでに一度、同じ構成で挫折しています。

普段の仕事はQAで、スマホを手で叩くテストが本業です。コードは1行も書きません。そういう立場から見た GitHub Actions の話を書きます。

補足

構成そのものを選んだ理由は、前回のnoteでもWordPressでもなく、Astro + S3 + CloudFrontでブログを書く理由に書きました。この記事は、その中で「これから」と書いた自動デプロイの部分です。

実は、一度挫折している

Astro + S3 + CloudFront でブログを作るのは、実は今回が初めてではありません。前にも一度作って、続きませんでした。

理由ははっきりしていて、公開するのが面倒すぎたからです。

記事を1本直すたびに、毎回これをやっていました。

以前の手作業と、今の git push だけの対比以前は記事を直すたびに、1. npm run build、2. dist をS3にアップロード、3. CloudFrontのキャッシュを消す、4. ブラウザで表示を確認、の4つを毎回手でやっていた。今は git push するだけで、あとは GitHub Actions が同じことをやってくれる。以前:毎回ぜんぶ手でもうやらない1npm run build2dist/ をS3にアップロード3CloudFrontのキャッシュを消す4ブラウザで表示を確認今:これだけgit push するだけあとは GitHub Actions がやる
以前は記事を直すたびに4つの手作業。今は git push だけ

書くより、公開するほうが疲れる。しかも誤字を1文字直すだけでも、同じ4つをやり直しです。そのうち「これは明日でいいか」になって、更新が止まりました。

技術的に難しかったわけではありません。ただ面倒だった。それだけで人は続けられなくなる、というのが前回の学びです。

自動化してみたい

今回また同じ構成を選んだので、放っておくと同じところで挫折します。

やりたいことは1つだけでした。記事を書いたら、あとは勝手に公開されてほしい。

ビルドもアップロードもキャッシュ削除も、毎回まったく同じ作業です。同じことを同じ順番でやるなら、機械のほうが速いし間違えない。人間がやる意味がありません。

そこで GitHub Actions を使いました。

GitHub Actions とは?

ざっくり言うと、**GitHubの中にいる「言われた作業をやってくれる係」**です。

  • git push などをきっかけに、勝手に動き出す
  • 動くのは自分のPCではなく、GitHubが用意する使い捨てのLinuxマシン
  • 作業が終わると、そのマシンごと消える
  • 個人のパブリックリポジトリなら無料

大事なのは、自分のPCが関係ないところです。極端な話、出先でGitHubのWeb画面から誤字を直しても、そこから先は同じように公開まで進みます。

CI/CDという言葉は資格の勉強で見ていましたが、「テストが自動で走るやつ」くらいの理解でした。実物はこれか、というのが正直な感想です。

おまけ:AWSで運用しているのに、デプロイはGitになる

自動化のおまけなのですが、この形にするとデプロイの操作が、AWSのコンソールではなくGitになります。

置き場所はS3とCloudFrontなのに、私が触るのは git addgit commitgit push だけ。AWSの画面はほとんど開きません。

Gitを日常的に使う機会が今はほとんどないので、これは自分にとってちょうどいい練習になります。記事を書けば書くほど、勝手にGitを触ることになる。前回の記事で「Gitの練習をしたかった」と書きましたが、その部分がここで回収できました。

動かすには、決まった場所にファイルを1枚置く

GitHub Actions に「何をしてほしいか」を伝える方法は、設定画面ではなくファイルです。

テキスト
リポジトリ/
└── .github/
    └── workflows/
        └── deploy.yml   ← これを置く

置き場所が決まっていて、.github/workflows/ の下にある .yml ファイルを、GitHubが勝手に見つけて実行します。ファイル名(deploy)のほうは自由です。

つまり、このファイルを置いてpushした時点で、自動化は始まります。 私がやったのも、結局はこれだけでした。

ymlって、Dockerでも見たやつ

拡張子の .yml を見て、あ、これ知ってる、となりました。Dockerを触ったときに出てきた docker-compose.yml と同じ形式です。

YAMLという書き方で、字下げで親子関係を表す、設定を書くためのものです。中身がプログラムというより箇条書きのメモに近いので、コードを書かない人間でも読めます。

DockerにもGitHub Actionsにも出てくるということは、自動化まわりでは何かとこれが出てくるということなんだと思います。覚えておいて損はなさそうです。

まず、deploy.yml の全容

訳す前に、まずファイルまるごとを載せます。これが今回追加した1枚です。

YAML
name: Deploy to S3

on:
  push:
    branches: [main]
  workflow_dispatch:

permissions:
  id-token: write
  contents: read

env:
  AWS_REGION: ap-northeast-1
  S3_BUCKET: バケット名
  DISTRIBUTION_ID: ディストリビューションID
  ROLE_ARN: arn:aws:iam::アカウントID:role/gha-astro-deploy

jobs:
  deploy:
    runs-on: ubuntu-latest
    steps:
      - name: Checkout
        uses: actions/checkout@v4

      - name: Setup Node
        uses: actions/setup-node@v4
        with:
          node-version: 22
          cache: npm

      - name: Install dependencies
        run: npm ci

      - name: Build
        run: npm run build

      - name: Configure AWS credentials
        uses: aws-actions/configure-aws-credentials@v4
        with:
          role-to-assume: ${{ env.ROLE_ARN }}
          aws-region: ${{ env.AWS_REGION }}

      - name: Upload hashed assets
        run: |
          aws s3 sync ./dist/ s3://${{ env.S3_BUCKET }}/ \
            --cache-control "public,max-age=31536000,immutable" \
            --exclude "*" --include "_astro/*"

      - name: Upload HTML and others
        run: |
          aws s3 sync ./dist/ s3://${{ env.S3_BUCKET }}/ --delete \
            --cache-control "public,max-age=0,must-revalidate" \
            --exclude "_astro/*"

      - name: Invalidate CloudFront cache
        run: |
          aws cloudfront create-invalidation \
            --distribution-id ${{ env.DISTRIBUTION_ID }} \
            --paths "/*"

これで全部です。60行もありません。

最初に見たときは記号だらけで身構えましたが、大きく4つのことしか書いていません。

ブロック 書いてあること
on: いつ動くか
permissions: 何をしてよいか
env: 使い回す値
jobs: どのマシンで、何を順番にやるか

deploy.yml を日本語に訳す

では、上から順に訳していきます。

① この自動化の名前

YAML
name: Deploy to S3

GitHubの画面に出る表示名です。動作には関係ないので、自分が分かる名前で大丈夫です。

② いつ動くか

YAML
on:
  push:
    branches: [main]
  workflow_dispatch:
  • push: branches: [main] — mainブランチにpushされたら動く
  • workflow_dispatch: — GitHubの画面から手動でも動かせるようにする(実行ボタンが出ます)

日本語にすると「mainにpushされたとき、あとはボタンを押されたときに動いて」です。

③ この作業に渡す権限

YAML
permissions:
  id-token: write
  contents: read
  • id-token: write — 「身分証」を発行してよい(後半のOIDCで使います)
  • contents: read — リポジトリの中身は読むだけ。書き換えはさせない

必要なことだけできる状態で動いて」という指定です。

id-token: write を忘れない

この1行が無いとGitHubが身分証を発行してくれず、AWSに繋ぐところで必ず失敗します。忘れやすさナンバーワンの行です。

④ 使い回す値に名前を付ける

YAML
env:
  AWS_REGION: ap-northeast-1
  S3_BUCKET: バケット名
  DISTRIBUTION_ID: ディストリビューションID
  ROLE_ARN: arn:aws:iam::アカウントID:role/gha-astro-deploy

このあと何度も出てくる値に、先に名前を付けています。「東京リージョンの、このバケットに、このCloudFrontで、この役割を借りて」。引っ越しや作り直しがあっても、直すのはここだけで済みます。

なお、ここに書いているのはパスワードの類ではないので、ファイルに直接書いています。秘密にしたい値は GitHub の Secrets に入れる仕組みがありますが、今回は1つも使いません。

⑤ どのマシンで動かすか

YAML
jobs:
  deploy:
    runs-on: ubuntu-latest
    steps:

jobs: が作業のまとまり、deploy はそれに付けた名前です。runs-on: ubuntu-latest は「最新のUbuntuの使い捨てマシンでやって」。

この下の steps: に、やってほしい手順を上から並べていきます。ここから先は、私が手でやっていた4つとほぼ同じです。

⑥ 記事を取ってきて、Nodeを用意する

YAML
      - name: Checkout
        uses: actions/checkout@v4

      - name: Setup Node
        uses: actions/setup-node@v4
        with:
          node-version: 22
          cache: npm
  • Checkout — まっさらなマシンに、リポジトリの中身をコピーする
  • Setup Node — Node.js 22 を入れる。cache: npm は「前回ダウンロードしたものを使い回して、速くして」

uses:他の人が作った部品を借りるという意味です。この2つは定番なので、名前を書くだけで済みます。

⑦ ビルドする

YAML
      - name: Install dependencies
        run: npm ci

      - name: Build
        run: npm run build

run: は「このコマンドを打って」。手元でやっていた npm run build と同じものが、使い捨てマシンの中で走ります。

npm ci は、package-lock.json のとおりに部品を入れ直すインストールです。これで dist/ ができあがります。手作業の1つ目が終わりました。

⑧ AWSに入る鍵を借りる

YAML
      - name: Configure AWS credentials
        uses: aws-actions/configure-aws-credentials@v4
        with:
          role-to-assume: ${{ env.ROLE_ARN }}
          aws-region: ${{ env.AWS_REGION }}

④で名前を付けた役割(ロール)を借りて、AWSを操作できる状態にします。

見てのとおり、アクセスキーは1文字も書いていません。 ここが今回のもう1つのポイントなので、あとでまとめて書きます。

⑨ S3にアップロードする(2回に分ける)

YAML
      - name: Upload hashed assets
        run: |
          aws s3 sync ./dist/ s3://${{ env.S3_BUCKET }}/ \
            --cache-control "public,max-age=31536000,immutable" \
            --exclude "*" --include "_astro/*"

      - name: Upload HTML and others
        run: |
          aws s3 sync ./dist/ s3://${{ env.S3_BUCKET }}/ --delete \
            --cache-control "public,max-age=0,must-revalidate" \
            --exclude "_astro/*"

aws s3 sync は「このフォルダと同じ状態にS3を揃えて」。手作業の2つ目です。

同じことを2回やっているように見えますが、分けているのはキャッシュの持たせ方が違うからです。

対象 キャッシュ 理由
_astro/ の中 1年 ファイル名にハッシュが入るので、中身が変われば名前も変わる。古いものを掴む心配がない
HTMLなど 毎回確認 更新したら、すぐ反映されてほしい

性質が違うものを、別々に扱っています。

もう1つ、後半にだけ --delete が付いています。これは「ローカルに無いファイルは、S3からも消して」。記事を削除したときに、残骸が残りません。

⑩ 古いキャッシュを消す

YAML
      - name: Invalidate CloudFront cache
        run: |
          aws cloudfront create-invalidation \
            --distribution-id ${{ env.DISTRIBUTION_ID }} \
            --paths "/*"

CloudFrontが世界中に配っている古いコピーを、捨てさせます。--paths "/*" は「全部」という意味です。手作業の3つ目がこれで終わりです。

これで、最初に載せた全文を上から最後までたどったことになります。この1枚で、私が毎回手でやっていた作業が、まるごと置き換わりました。

アクセスキーを置かなくていい(OIDC)

⑧で飛ばした「鍵を借りる」部分です。

ふつうは、AWSのアクセスキーを GitHub の Secrets に保存して使います。ただ、保存している以上は漏れる可能性がありますし、自分で消さない限りずっと有効です。リポジトリはパブリックにしたかったので、できれば置きたくありませんでした。

そこで OIDC という仕組みを使いました。やっていることは、こんなイメージです。

  1. 実行のたびに、GitHubが「これは○○さんの、このリポジトリの、mainブランチの作業です」という身分証を出す
  2. AWSがそれを確認して、合っていれば1時間だけ有効な鍵を貸す
  3. 作業が終われば、その鍵は勝手に切れる

保存する鍵がないので、漏れる鍵もありません。 事前にAWS側で「GitHubを信用します」という登録と、貸し出す役割(ロール)を1つ作っておくだけです。

補足

この登録まわりで見事にハマって2時間ほど溶かしたのですが、長くなるので別の記事に分けます。

できあがったもの

git push してから記事が読まれるまでの流れローカルで git push すると、GitHub Actions がビルドしてS3へ同期する。S3は直接は公開せず、CloudFrontだけが読める状態のまま。CloudFrontがHTTPSで世界に配信し、読む人には1分後には新しい記事が届く。ローカルgit push するだけGitHub ActionsビルドしてS3へ同期S3直接は公開しないCloudFrontHTTPSで世界に配信読む人1分後には新しい記事
git push してから記事が読まれるまでの流れ(自分でやるのは一番上だけ)

いま記事を公開するときにやっているのは、この3行だけです。

Bash
git add .
git commit -m "記事を追加"
git push

30秒から1分で、公開まで終わります。誤字を1文字直すときも同じ。前回挫折した原因が、まるごと無くなりました。

面倒だからやらない、が起きない。 今度は続きそうです。