はじめに
React + Hono + Cloudflare Workers + D1 で作っている近況共有アプリに、GitHub Actions の CI/CD を入れました。
やりたかったことはシンプルです。
develop へ入れる前に壊れていないか確認する
main へ入れる前にも確認する
main に入ったら本番へデプロイする
今回は、次のような運用にしました。
feature branch -> develop
CI: lint / build / migration check
develop -> main PR
CI: lint / build / migration check
main push
CD: D1 migration apply / Workers deploy / smoke test
今回の構成
アプリの構成はざっくりこんな感じです。
Frontend: React + Vite
API: Hono
Runtime: Cloudflare Workers
DB: Cloudflare D1
Migration: Drizzle
Deploy: Wrangler
package.json にはもともと次のような script がありました。
{
"scripts": {
"build": "tsc -b && vite build",
"lint": "eslint .",
"db:generate": "drizzle-kit generate",
"cf:typegen": "wrangler types"
}
}
ここに migration の整合性チェック用として db:check を追加しました。
{
"scripts": {
"db:check": "drizzle-kit check"
}
}
drizzle-kit check は、Drizzle の migration メタデータに破綻がないか確認するために使っています。
CIでやること
CIでは、本番へ出す前の最低限の確認をします。
npm ci
npm run lint
npm run build
npm run db:check
テストがある場合はここに npm test も入れる想定です。
今回作った CI workflow は次のような形です。
name: CI
on:
pull_request:
branches:
- develop
- main
push:
branches:
- develop
permissions:
contents: read
concurrency:
group: ci-${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: true
jobs:
verify:
name: Build, lint, and migration check
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v4
- name: Setup Node.js
uses: actions/setup-node@v4
with:
node-version: 22
cache: npm
- name: Install dependencies
run: npm ci
- name: Lint
run: npm run lint
- name: Build
run: npm run build
- name: Check Drizzle migrations
run: npm run db:check
pull_request は develop と main 向けにしています。
日常開発では develop へ入れる前に確認し、リリース時には main へ入れる前にも同じ確認をします。
push は develop だけにしています。
main への push は CD 側で deploy まで行うため、CI専用workflowではなく deploy workflow に寄せています。
CDでやること
CDは main に入ったときだけ動かします。
本番反映では、次の順番にしました。
npm ci
npm run lint
npm run build
npm run db:check
npx wrangler d1 migrations apply kinkyo-note-db --remote
npx wrangler deploy
curl /api/health
deploy前にも lint / build / migration check をもう一度走らせています。
PR時点でCIは通っているはずですが、main に入った実際のコードで確認してからデプロイしたいので、CD側でも同じチェックを入れています。
workflow は次のようにしました。
name: Deploy
on:
push:
branches:
- main
workflow_dispatch:
permissions:
contents: read
concurrency:
group: production-deploy
cancel-in-progress: false
jobs:
deploy:
name: Deploy to Cloudflare Workers
runs-on: ubuntu-latest
environment: production
env:
CLOUDFLARE_API_TOKEN: ${{ secrets.CLOUDFLARE_API_TOKEN }}
CLOUDFLARE_ACCOUNT_ID: ${{ secrets.CLOUDFLARE_ACCOUNT_ID }}
steps:
- name: Checkout
uses: actions/checkout@v4
- name: Setup Node.js
uses: actions/setup-node@v4
with:
node-version: 22
cache: npm
- name: Install dependencies
run: npm ci
- name: Lint
run: npm run lint
- name: Build
run: npm run build
- name: Check Drizzle migrations
run: npm run db:check
- name: Apply D1 migrations
run: npx wrangler d1 migrations apply kinkyo-note-db --remote
- name: Deploy Worker
run: npx wrangler deploy
- name: Smoke test
run: |
curl --fail --retry 5 --retry-delay 3 \
https://kinkyo-note.bobu2784.workers.dev/api/health
workflow_dispatch も入れているので、必要なら手動でもデプロイできます。
GitHub Secretsを登録する
GitHub Actions から Cloudflare へデプロイするには、Cloudflare の認証情報が必要です。
GitHub の repository secrets に次の2つを登録しました。
CLOUDFLARE_API_TOKEN
CLOUDFLARE_ACCOUNT_ID
GitHub では次の場所から登録できます。
Settings
-> Secrets and variables
-> Actions
-> New repository secret
CLOUDFLARE_API_TOKEN は Cloudflare 側で作った API token です。
今回必要な権限は、ざっくり次の2つです。
Account -> Workers Scripts -> Edit
Account -> D1 -> Edit
wrangler deploy で Workers をデプロイし、wrangler d1 migrations apply で D1 migration を適用するためです。
CLOUDFLARE_ACCOUNT_ID は Cloudflare の対象アカウントIDです。
production Environmentを作る
GitHub Actions の deploy job では、次のように environment: production を指定しています。
environment: production
そのため、GitHub 側で production Environment を作りました。
Settings
-> Environments
-> New environment
-> production
ここでは、デプロイ前に承認を挟むために Required reviewers を有効にしました。
Required reviewers: ON
Reviewer: 自分
これで、main に merge されたあと、deploy job が本番反映の直前で承認待ちになります。
個人開発なので Prevent self-review はOFFにしています。
また、production environment を使えるブランチは main のみに制限しました。
Deployment branches and tags
Selected branches and tags
main
これで、誤って別ブランチから production deploy されるのを防げます。
Cloudflare側のSecretsとは別
ここで少しややこしいのが、GitHub Secrets と Cloudflare Worker Secrets は別物という点です。
GitHub Secrets は、GitHub Actions が Cloudflare にデプロイするために使います。
CLOUDFLARE_API_TOKEN
CLOUDFLARE_ACCOUNT_ID
一方で、アプリが実行時に使う秘密情報は Cloudflare Workers 側に設定します。
たとえば今回のアプリでは、LINEログインやセッションで次のような値が必要です。
LINE_CHANNEL_SECRET
SESSION_SECRET
これは GitHub Actions のためではなく、Worker runtime のための secret です。
設定する場合は、Cloudflare dashboard か wrangler secret put を使います。
npx wrangler secret put LINE_CHANNEL_SECRET
npx wrangler secret put SESSION_SECRET
D1 migrationをCDに含める
今回は main への反映時に、D1 migration も自動で適用するようにしました。
npx wrangler d1 migrations apply kinkyo-note-db --remote
--remote を付けることで、ローカルD1ではなくCloudflare上のD1に対して migration を適用します。
wrangler d1 migrations apply は、通常は確認プロンプトが出ます。
ただし CI/CD のような非対話環境では確認ステップがスキップされるため、GitHub Actions 上でも実行できます。
Smoke testを入れる
デプロイ後に、最低限APIが生きているか確認するため /api/health を叩いています。
curl --fail --retry 5 --retry-delay 3 \
https://kinkyo-note.bobu2784.workers.dev/api/health
--fail を付けているので、HTTPエラーなら workflow が失敗します。
--retry も付けて、デプロイ直後の一時的な揺れを少し吸収します。
本当はログインや投稿まで含めたE2Eテストがあるとより安心ですが、まずは軽い smoke test から始めました。
branch protectionも設定したい
workflow を作っただけだと、main へ直接pushできる設定のままかもしれません。
運用としては、GitHub の branch protection も設定した方が安全です。
おすすめは次のような設定です。
main
PR必須
CI成功必須
直接push禁止
develop
PR必須
CI成功必須
これで、CIを通っていないコードが main に入るのを防ぎやすくなります。
まとめ
今回のCI/CDでは、次のように役割を分けました。
develop向けPR
日常開発の品質チェック
main向けPR
リリース直前の品質チェック
main push
本番DB migration
Workers deploy
smoke test
最初から複雑なパイプラインにせず、まずは次の4つを押さえる形にしました。
lint
build
migration check
deploy + health check
Cloudflare Workers + D1 の構成でも、GitHub Actions と Wrangler を使えば、比較的シンプルにCI/CDを組めました。