💸

VercelへのデプロイをGitHub Actions経由で行うことでVercelチームメンバー以外でも開発に参加できるようにする

2023/04/20に公開4

TL;DR

  • フロントエンドをVercelでホストするプロジェクトをチームで開発していた
  • Vercelにチームを作り、開発メンバー全員をチームに招待して、月額$20×人数を支払っていた
  • なぜなら、Vercelの標準のGitHub連携を使う場合、GitHubにコミットするメンバーは全員Vercelのチームメンバーである必要がある ため
    • GitHub連携をしている状態で、Vercelのチームメンバーでない人がGitHubにコミットをpushすると、Vercelへのプレビューデプロイが失敗してCIが赤くなる
  • 自分以外の開発メンバーはVercelのダッシュボードを触ることはまったくないのに、GitHub連携のためだけに全員分の $20 を払うのはさすがにもったいなかった
  • そこで、Vercelの標準のGitHub連携を使うのをやめて、自分のアクセストークンを使ってGitHub Actions経由でVercelへデプロイするように設定し、自分以外の開発メンバーはVercelのチームメンバーでなくした

Vercel標準のGitHub連携の問題点(?)

皆さんご存知のとおり、Vercelには標準でGitHubなどのGitリポジトリと連携してデプロイを自動化してくれる機能があります。

この画面の GitHub ボタンからリポジトリを選んでセットするだけで、そのリポジトリへのコミットが自動でVercelにデプロイされるようになり、さらに Production Branch で指定したブランチへのコミットの場合のみ自動でProduction環境にプロモートしてくれるようになります。

便利すぎますね。

しかしチーム開発でこの機能を使う場合、Vercelのプロジェクトを所有するチームのメンバーでないGitHubユーザーがコミットをpushすると、そのコミットに対するVercelへのプレビューデプロイが権限不足により失敗してしまいます。

これを回避するためには、GitHubリポジトリにコミットをpushする可能性のある開発者全員をVercelチームのメンバーにしておく必要があります。

2023年4月現在の Vercelの料金体系 では、チームメンバーは全員最低でもProプラン($20/mo)でなければならないので、単純に 月額$20×開発者の人数 のお金が必要になります。

サービスを便利に使わせてもらう以上、然るべき対価を支払うのは当然のことなのですが、開発者全員がVercel自体のダッシュボードとかを触りたいわけでもないのに、デプロイを自動化するためだけに、ごく稼働の少ない人も含めて全員に対して一律$20/moというのは、さすがに財布が辛すぎるというのが正直な感想です…💸

Vercelチームメンバーを自分一人にして大幅節約

よくよく調べてみると、以下の公式ドキュメントにてGitHub Actions経由で手動でVercelにデプロイする方法が紹介されていました。

How to Use GitHub Actions to Deploy to Vercel

これを使えば、自分以外の開発メンバーをVercelチームのメンバーから外しても、標準のGitHub連携を使っているときと同じ体験が得られそうです。

細かい説明は後回しにして先に結論を言うと、最終的に以下のようなワークフローを書くことで期待どおりの結果を得ることができました🙌

# .github/workflows/ci.yaml

name: CI

env:
  VERCEL_ORG_ID: ${{ secrets.VERCEL_ORG_ID }}
  VERCEL_PROJECT_ID: ${{ secrets.VERCEL_PROJECT_ID }}

on: push

jobs:
  test:
    # 略

  preview:
    if: ${{ github.ref != 'refs/heads/release' }}
    needs: test
    runs-on: ubuntu-latest
    environment:
      name: preview
      url: ${{ steps.deploy.outputs.url }}
    steps:
      - uses: actions/checkout@v3

      - name: Setup node
        uses: actions/setup-node@v3
        with:
          node-version: 18
          check-latest: true

      - name: Install Vercel CLI
        run: npm install --global vercel@latest

      - name: Pull Vercel Environment Information
        run: vercel pull --yes --environment=preview --token=${{ secrets.VERCEL_TOKEN }}

      - name: Build Project Artifacts
        run: vercel build --token=${{ secrets.VERCEL_TOKEN }}

      - name: Deploy Project Artifacts to Vercel
        id: deploy
        run: echo "url=$(vercel deploy --prebuilt --token=${{ secrets.VERCEL_TOKEN }})" >> $GITHUB_OUTPUT

      - name: Assign staging domain to deployment (if main branch)
        if: ${{ github.ref == 'refs/heads/main' }}
        run: vercel alias ${{ steps.deploy.outputs.url }} stg.my-service.com --scope=my-team --token=${{ secrets.VERCEL_TOKEN }}

  prod:
    if: ${{ github.ref == 'refs/heads/release' }}
    needs: test
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v3

      - name: Setup node
        uses: actions/setup-node@v3
        with:
          node-version: 18
          check-latest: true

      - name: Install Vercel CLI
        run: npm install --global vercel@latest

      - name: Pull Vercel Environment Information
        run: vercel pull --yes --environment=production --token=${{ secrets.VERCEL_TOKEN }}

      - name: Build Project Artifacts
        run: vercel build --prod --token=${{ secrets.VERCEL_TOKEN }}

      - name: Deploy Project Artifacts to Vercel
        run: vercel deploy --prod --prebuilt --token=${{ secrets.VERCEL_TOKEN }}

大枠は

How to Use GitHub Actions to Deploy to Vercel

に書かれているとおりです。

ただし、この例では、本番にデプロイするブランチは main ではなく release です。

また、テストのジョブに依存(needs)させたかったので、ドキュメントの例とは違って1ファイルにまとめた上でジョブの if 文で対象のブランチを分けています。

workflow_run を使えば別ファイルのワークフローに依存したワークフローを書けるので必ずしも1ファイルにまとめる必要はないのですが、個人的にこっちのほうが慣れてて分かりやすいのでこうしました。

上記ドキュメントで言及されていないこともいくつかやっています。

まずは preview ジョブの Deploy Project Artifacts to Vercel ステップに注目してください。

- name: Deploy Project Artifacts to Vercel
  id: deploy
  run: echo "url=$(vercel deploy --prebuilt --token=${{ secrets.VERCEL_TOKEN }})" >> $GITHUB_OUTPUT

vercel deploy コマンドを実行するだけでなく、そのの出力結果(デプロイメントのURL)を GITHUB_OUTPUT 環境変数に url という名前を付けて格納しています。

この結果が、preview ジョブの定義冒頭部分の

environment:
  name: preview
  url: ${{ steps.deploy.outputs.url }}

で利用されており、これによって preview という名前のデプロイ環境にVercelのプレビューデプロイのURLが紐づき、結果としてPRのConversationタブに下図のような行が出力され、PRの画面から直接プレビューデプロイを見に行くことができるようになります。

参考

また、もう一点特筆すべき点として、preview ジョブの Assign staging domain to deployment (if main branch) ステップに注目してください。

- name: Assign staging domain to deployment (if main branch)
  if: ${{ github.ref == 'refs/heads/main' }}
  run: vercel alias ${{ steps.deploy.outputs.url }} stg.my-service.com --scope=my-team --token=${{ secrets.VERCEL_TOKEN }}

これは、main ブランチへのpushの場合のみ、vercel alias コマンドを使ってデプロイメントのURLにステージング環境のドメインをアサインするという処理です。

  • stg.my-service.com という部分は、ステージング環境のドメインのつもりで書いています
  • my-team という部分は、Vercelのチーム名のつもりで書いています

Vercel標準のGitHub連携では、特定のブランチに対してサブドメインを割り当てるということがドメインの設定画面から簡単にできました(下図)が、今回はこれも手動で行う必要があるわけです。

また、そもそもですが、

How to Use GitHub Actions to Deploy to Vercel

に書かれているとおり、このワークフローが実際に動作するには、VERCEL_TOKEN VERCEL_ORG_ID VERCEL_PROJECT_ID という3つのシークレットが設定されている必要があります。

VERCEL_TOKENhttps://vercel.com/account/tokens で作成したもの、VERCEL_ORG_ID VERCEL_PROJECT_ID はローカルで vercel login && vercel link して作成された .vercel/project.json から得られるものをそれぞれ設定すればOKです。

まとめ

というわけで、VercelへのデプロイをGitHub Actions経由で行うことでVercelチームメンバー以外でも開発に参加できるようにする(結果的に大幅に費用を節約する)方法を解説しました。

他の開発メンバーのコミットもすべて自分のアクセストークンを使ってデプロイされるようになるので、あまりに規模の大きいプロジェクトでは1日のデプロイ数の制限(Proプランだと3,000)に気をつける必要があるかもしれませんが、多くのケースでほとんど問題にならないでしょう。

以上、お役に立てば幸いです。

GitHubで編集を提案

Discussion

tomoftomof

ありがとうございます!参考にさせていただきました🙏
一箇所想定どおりに動かない箇所があり、

run: echo "url=$(vercel deploy --prebuilt --token=${{ secrets.VERCEL_TOKEN }})" >> $GITHUB_OUTPUT

が自分の場合はうまく動作してくれませんでした。

参考とされていたHow to deploy to Vercel with GitHub Actionsに沿って、下記のように変更したところ、想定どおりにView deploymentが表示されました。

run: echo "::set-output name=url::$(vercel deploy --prebuilt --token=${{ secrets.VERCEL_TOKEN }})"
ttskchttskch

コメントありがとうございます!

2022/10/11に

https://github.blog/changelog/2022-10-11-github-actions-deprecating-save-state-and-set-output-commands/

でアナウンスされているとおり、 ::set-output はすでに非推奨となっており、本稿で使用した

run: echo "変数名=値" >> $GITHUB_OUTPUT

が新しく導入された正しい記法となっています。

::set-output (および ::save-state )はもともと2023/05/31をもって削除される予定でしたが、影響度の大きさを鑑みて削除時期が延期され、今は延命期間中です。

リンク先にも記載があるとおり、self-hosted runner を使っている場合にはバージョンの指定を 2.297.0 以上にしないと新しい記法が使えるようにならないようなので、動かないのはそれが原因でしょうかね・・

ubuntu-latest などの GitHub-hosted runner を使っている場合には特に何もしなくても新しい記法が使える認識です。

tomoftomof

::set-output はすでに非推奨となっており、本稿で使用した

このことは把握していませんでした。ありがとうございます🙏
ただ、ubuntu-latest なんですけど、想定どおり動かなかったんですよね…。
何かまた進展があればコメントさせていただきます。

ttskchttskch

そしたら、ちょっと謎ですね、、😓
もし何か分かったら教えていただけるとありがたいです🙏