はじめに
GitHub Organization で管理するリポジトリ数が20を超えてくると、同じ設定ファイル(dependabot.yml や CODEOWNERS など)を全リポジトリに配布する仕組みの重要度が上がります。手動運用のままだとリポジトリ追加のたびにコピーが必要になり、Dependabot 設定の更新も全リポジトリに手動で PR を立てることになるためです。
今回、社内で dependabot.yml をまとめて配信する基盤を整備しました。この記事では、設計の考え方、実装、仕様の深掘りを通して確立した運用ルール、権限設計の方針をまとめます。
前提となる技術スタック:
- GitHub Organization(中〜大規模、数十リポジトリ)
- GitHub Apps / GitHub Actions / Organization Rulesets
- 外部 Action として
BetaHuhn/repo-file-sync-action
中央集約が必要な理由
GitHub には .github という予約された組織リポジトリがあり、 CONTRIBUTING.md や Issue / PR テンプレートなど、いくつかのファイルを organization 全体のフォールバックとして配布できます。
ところが dependabot.yml はこのフォールバック機構の対象外。.github 配下に置いても、各リポジトリでは有効化されません。各リポジトリの .github/dependabot.yml を個別に置く必要があります。
この制約を踏まえ、以下を満たす配信基盤を設計しました。
-
.githubリポジトリで設定を 一元管理 - 配信先リポジトリを YAML で明示
- 配信は GitHub Actions で自動化、PR 経由で監査性を保つ
- 認証は 専用の GitHub App(PAT は使わない)
全体構成
.github/
├── dependabot.yml # 配信ソース兼、自リポジトリ用の設定
├── sync-config.yml # 配信先リポジトリの定義
└── workflows/
└── sync-dependabot.yml # 配信ワークフロー
フロー:
-
.github/dependabot.ymlを編集 → main に push - GitHub Actions が発火し、
sync-config.ymlにある全リポジトリに対して sync branch を作成 - 各リポジトリに同期 PR が作成される
- PR がマージされれば各リポジトリで Dependabot が稼働
配信フロー図
実装
配信元の dependabot.yml
機能要件は以下の4点。
- 週次で依存関係更新をチェック
- 脆弱性対応とは別チャンネルとして扱う(アラートは即座、アップデート PR は週次)
- クールダウン期間で新リリース直後の地雷バージョンを避ける
- GitHub Actions のアップデートは1つの PR に集約してレビュー負担を下げる
version: 2
updates:
- package-ecosystem: github-actions
directory: /
schedule:
interval: weekly
open-pull-requests-limit: 10
cooldown:
default-days: 7
groups:
github-actions:
patterns:
- "*"
cooldown.default-days: 7 は新バージョンがリリースされてから7日間経過するまで PR を作らない設定です。サプライチェーン攻撃対策として有効で、かつ脆弱性修正(security updates)には適用されないため安全性とバランスが取れています。
配信先の sync-config.yml
repo-file-sync-action が参照する設定ファイル。
group:
files:
- source: .github/dependabot.yml
dest: .github/dependabot.yml
repos: |
owner/repo-a
owner/repo-b
owner/repo-c
# ...全リポジトリを列挙
リポジトリ追加時はここを編集するだけで済む設計です。
配信ワークフロー
name: Sync dependabot.yml to all repos
on:
push:
paths:
- .github/dependabot.yml
- .github/sync-config.yml
branches:
- main
workflow_dispatch:
jobs:
sync:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@<SHA> # v6.0.2
- uses: actions/create-github-app-token@<SHA> # v3.1.1
id: app-token
with:
app-id: ${{ vars.OPS_BOT_APP_ID }}
private-key: ${{ secrets.OPS_BOT_PRIVATE_KEY }}
owner: <your-org>
- uses: BetaHuhn/repo-file-sync-action@<SHA> # v1.21.1
with:
GH_INSTALLATION_TOKEN: ${{ steps.app-token.outputs.token }}
CONFIG_PATH: .github/sync-config.yml
COMMIT_PREFIX: "chore: "
COMMIT_EACH_FILE: false
PR_BODY: |
(PRの説明文)
PR_LABELS: sync
TEAM_REVIEWERS: <team-slug>
Action は SHA pin で固定しました。tag は後から付け替えが可能なため、サプライチェーン対策として SHA で固定するのが安全です。
paths 指定により、ワークフロー自体(sync-dependabot.yml)を変更しても発火しません。source である dependabot.yml か配信先一覧の sync-config.yml が変更された場合のみ動きます。
GitHub App の設計と命名体系
組織自動化のための GitHub App を作るとき、用途ごとに分割するか、汎用 App で統合するか は重要な設計判断です。
比較軸
| 観点 | 単一 App | 用途別に分割 |
|---|---|---|
| 権限最小化 | 肥大化しやすい | 必要最小限を個別付与 |
| 秘密鍵漏洩時の影響範囲 | 全機能に波及 | 機能単位に限定 |
| 管理コスト | 小 | 中(App作成・Secret管理が増える) |
| 通知・監査の識別性 | 全操作が同じ bot 名義 | 機能ごとに識別可能 |
| 権限追加時の Org 承認 | 既存 App 権限の拡張が必要 | 新 App 作成で独立 |
採用した方針: 「汎用 bot + 将来の特化 bot への分離経路」
今回は以下の方針を採用しました。
-
日常的な運用自動化は汎用 App にまとめる(
*-ops-botとして命名) -
本番影響が大きい操作は専用 App に切り出す(将来の
*-deploy-bot/*-admin-bot/*-notify-bot)
ops という語は DevOps 文脈では広義の「運用自動化」として使われ、GitHub 自動化の文脈にも馴染みます。
汎用 App の守備範囲
- PR / Issue の非破壊的な自動化(ラベル、コメント、アサイン、Project 追加)
-
.github配下のテンプレート・設定ファイル配信 - リリースノート / Changelog のドラフト生成
将来、専用 App に切り出す領域
- 本番環境のデプロイ / インフラ変更操作
- Org Admin 権限(メンバー管理、リポジトリ作成)
- 外部サービス連携(Slack 通知、Webhook 受信)
- 第三者 Action に秘密鍵を渡すケース
この分離方針を 命名段階で表明しておくこと が重要でした。将来専用 App を追加したときに、汎用 bot と並立する命名階層(*-ops-bot / *-deploy-bot / *-notify-bot)が自然に成立するためです。
Variables / Secrets の権限最小化
Organization Variables と Secrets には可視性の選択肢があります。
- All repositories: Org 内すべてのリポジトリからアクセス可能
- Private repositories: Private リポジトリからのみアクセス可能
- Selected repositories: 指定したリポジトリのみアクセス可能
今回の配信基盤は .github リポジトリのワークフローでのみ使う設計です。他リポジトリからアクセスする必要がないため、Selected repositories で .github のみを指定 しました。
Visibility: Selected repositories
└── .github のみ
権限最小化の原則に沿った設定で、秘密鍵が仮にワークフローログから漏れても影響範囲は .github リポジトリに閉じます。
Secret は 書き込み専用仕様(一度登録した値を読み取る API はない)なので、「旧 Secret から新 Secret に値をコピー」という操作は直接できません。秘密鍵の扱いは以下を運用ルールとしました。
- 秘密鍵
.pemはパスワードマネージャに保管し、ダウンロードフォルダには放置しない - Secret への貼り付け後はクリップボードをクリアする
macOS なら:
pbcopy < ~/path-to/app-private-key.pem
# GitHub UI に Cmd+V で貼り付け
pbcopy < /dev/null # 貼り付け後にクリップボード掃除
クリップボード履歴アプリ(Paste / Alfred / Raycast 等)に平文の秘密鍵が残る事故を防ぐため、貼り付け後の掃除は必須としています。
action の動作仕様を深掘りする
配信基盤を安定運用するには、BetaHuhn/repo-file-sync-action の動作仕様を正確に把握しておく必要があります。公式 README、action.yml、src/git.js を照合して挙動を確定させました。
使用した input と確定した挙動
自分たちの sync-dependabot.yml で指定している input と、その挙動を整理します。
| input | 指定値 | 挙動 |
|---|---|---|
SKIP_PR |
未指定(default: false) | sync branch を作成して PR を開く。明示的に true にしない限り default branch への直接 push は発生しない |
BRANCH_PREFIX |
未指定(default: repo-sync/SOURCE_REPO_NAME) |
repo-sync/<source>/<branch> 形式の sync branch が自動生成される |
CONFIG_PATH |
.github/sync-config.yml |
配信先リポジトリの定義ファイル |
TEAM_REVIEWERS |
<team-slug> |
PR 新規作成時・既存 PR 更新時の両方で reviewer 追加を試行する |
OVERWRITE_EXISTING_PR |
未指定(default: true) | 既存 PR の title と body のみ上書き。reviewer は温存される |
COMMIT_PREFIX |
chore: |
同期 commit のプレフィックス |
COMMIT_EACH_FILE |
false |
複数ファイル同期時に一つの commit にまとめる |
v1.21 の公式 input と照合した結果
v1.21 時点で有効な input は公式に定義されているものに限られます。未定義の input は warning で無視されるため、記述時に公式リストとの照合が必要でした。今回の実装では以下を意識しました。
- 未定義の input(例:
PR_TITLE)は使わない - 条件付きで動く input の前提を確認する
- 例:
COMMIT_AS_PR_TITLEはORIGINAL_MESSAGE: trueが前提(README に "Only works if ORIGINAL_MESSAGE is true and working" と明記)
- 例:
冪等性を前提とした運用
reviewer 追加が「新規作成時・既存 PR 更新時の両方で実行される」挙動を利用して、以下の運用にしました。
- 権限設計(App の Organization permissions や team collaborator)を後から整えたあとに、sync を再実行するだけで既存 PR にも reviewer が後付けされる
- 既存 PR を一度閉じて新規作成し直す必要はない
- 人間が手動で追加した reviewer は
OVERWRITE_EXISTING_PR: trueでも温存されるため、bot と人間の追加が共存できる
確立した運用ルール
以上の深掘りを踏まえ、以下をチームの標準プロセスに追加しました。
-
外部 Action を採用するときは README に加えて
action.ymlとsrc/まで精読する- 未定義 input の検出
- 条件付きオプションの前提確認
- default 値の挙動確認
- Action の SHA pin を徹底する
- 新規 Action のレビュー時は v1.x の breaking change に注意する
Organization Rulesets と bypass 設計
Organization 全体で Rulesets を運用する場合、bypass_actors の設計 が自動化 bot の動作に直結します。
Rulesets の挙動を把握する
Rulesets は以下の設定で どの branch・どのリポジトリに rule が適用されるか を定義します。
{
"conditions": {
"ref_name": {
"exclude": ["refs/heads/deployment/**/*", "refs/heads/apply/**/*"],
"include": ["~ALL"]
},
"repository_name": {
"include": ["~ALL"]
}
},
"rules": [{ "type": "pull_request", "parameters": { ... } }]
}
ref_name.include: ["~ALL"] を指定すると すべての branch が対象になります。これが今回の重要ポイントでした。
sync 系の action は repo-sync/<source> のような branch を作ってそこに push する 動作なので、全 branch 対象の Ruleset があると sync branch への push も rule が適用されます。pull_request rule(PR 経由必須)が全 branch にかかっていると、sync branch への push まで PR 経由を要求されてしまう構造です。
bypass_actors で自動化 bot を許容する
Ruleset には bypass_actors が用意されており、ここに登録された User / Team / App(Integration)は rule を回避できます。
| actor_type | 用途 |
|---|---|
User |
個別ユーザー(基本的に非推奨。Team 経由が良い) |
Team |
チーム単位。admin 権限運用と相性が良い |
Integration |
GitHub App 単位。自動化 bot の bypass はここに登録する |
actor_type: "Integration" を指定する場合、actor_id には App ID(Installation ID ではなく)を使います。ここは混乱しやすいポイントです。
採用した bypass 設計
以下の設計を採用しました。
-
default branch を守る Ruleset:
bypass_actorsは空。人間も bot もバイパスできず、main への直接 push は一切発生しない -
PR Approval を要求する Ruleset:
bypass_actorsに自動化 bot App を登録。bot は sync branch への push と PR 管理を自由に行える - Required workflow を要求する Ruleset: 同様に自動化 bot App を登録。必須ワークフローの完了を待たずに sync branch 操作ができる
実際の更新 API
Ruleset の更新は PUT /orgs/<org>/rulesets/<ruleset-id> を使います。PUT のペイロードには id / source / _links / created_at などの余計なフィールドを含めないよう、必要フィールドだけ抽出します。
gh api /orgs/<org>/rulesets/<ruleset-id> | \
jq '{
name, target, enforcement, conditions, rules,
bypass_actors: (.bypass_actors + [
{ "actor_id": <app-id>, "actor_type": "Integration", "bypass_mode": "always" }
])
}' > /tmp/ruleset_new.json
gh api /orgs/<org>/rulesets/<ruleset-id> -X PUT --input /tmp/ruleset_new.json
bypass_mode には always と pull_request があり、sync action のように直接 git push する自動化は always を選択 します。pull_request は「PR 経由の変更のみ bypass」なので、push は対象外になります。
確立した設計原則
-
Ruleset の適用範囲は明示的に最小化(
~DEFAULT_BRANCHやrefs/heads/mainに絞るのが基本) - 全 branch 対象にする場合は bot 用の bypass_actors を必ず設計 に含める
- default branch を守る Ruleset には bypass を入れない(最後の砦を残す)
- bypass は App 単位で行う(User / Team より影響範囲を絞れる)
Team Reviewer のための権限設計
配信 PR には team 単位の reviewer を自動付与する設計にしました。これを実現するには、以下の3層で権限が揃っている必要があります。
必要な3層の権限
-
GitHub App の Organization permissions: Members: Read
- Team の node ID を解決するために必要
- Org 側の owner 承認が要る
-
Team が各リポジトリの collaborator として登録されている
- 未登録の場合、reviewer リクエストが拒否される("Reviews may only be requested from collaborators")
-
Repository permissions: Pull requests: Write
- PR 自体の作成・編集
これらのうちどれか一つが欠けると reviewer 追加は API レベルで拒否されます。配信 PR の reviewer 追加を標準機能として使うなら、権限チェックをチェックリスト化しておくのが確実です。
team を全リポジトリに登録するスクリプト
for repo in $(cat .github/sync-config.yml | grep -oE "<org>/[a-zA-Z0-9_-]+"); do
gh api "/orgs/<org>/teams/<team-slug>/repos/${repo}" -X PUT -f permission=admin
done
permission は pull / triage / push / maintain / admin から選びます。既存 team 設定と揃えるのが運用上の一貫性として良いです。
採用した権限設計原則
- reviewer 指定を team にする場合は Org permission と collaborator 設定を必ず揃える
- 新規リポジトリ追加時のチェックリストに「配信対象 team を collaborator に追加」を入れる
- sync action の team reviewer 追加は冪等 なので、権限を揃えてから sync を再実行すれば既存 PR にも reviewer が後付けされる
自動マージの設計
配信 PR は機械的に生成される同一内容のファイル変更です。レビュー負荷を抑えつつ監査性を残す自動マージ設計を検討しました。
選択肢の比較
| 案 | 内容 | 採用可否 |
|---|---|---|
| GitHub Native の Auto-merge | 各リポジトリで有効化し、gh pr merge --auto --merge で設定。required checks と approval が揃えば自動マージ |
検討可 |
| Bot による admin merge | Ruleset の bypass_actors に bot を登録済みなので、gh pr merge --merge で approval 要件を bypass |
採用候補 |
SKIP_PR: true で直接 push |
PR を作らず default branch に直接 push | 不採用 |
SKIP_PR: true を採用しない理由
default branch を守る Ruleset には bypass_actors を空にしておく方針としたため、bot による直接 push は技術的にも不可能です。SKIP_PR: true にするには最後の砦となる Ruleset の bypass を開ける必要があり、サプライチェーン対策として避けるべき設計です。
採用する自動マージの条件
Bot による admin merge を採用する場合、以下を守ります。
-
merge method は
merge(Ruleset でallowed_merge_methods: ["merge"]を要求しているため、squash/rebaseは拒否される) - 各リポジトリの Required status checks をパスすることが前提(Lint や Test)
- 段階導入(初回配信は手動確認、定常運用から自動マージ)
- name: Auto-merge sync PRs
env:
GH_TOKEN: ${{ steps.app-token.outputs.token }}
run: |
while IFS= read -r repo; do
pr=$(gh pr list --repo "$repo" --author app/<bot-slug> --label sync --state open --json number --jq '.[0].number')
if [ -n "$pr" ]; then
gh pr merge "$pr" --repo "$repo" --merge
fi
done < <(yq '.group.repos[]' .github/sync-config.yml)
確立した運用ルール・原則
今回の設計を通じて、以下をチームの標準プロセスに追加しました。
外部 Action 導入時のチェックリスト
-
README / action.yml /
src/を精読し、有効 input と動作仕様を確定する - SHA pin で固定する(tag は後から差し替え可能なため)
-
条件付きオプションの前提条件を把握する(例:
COMMIT_AS_PR_TITLEのORIGINAL_MESSAGE前提)
GitHub App 設計の原則
- 命名は将来の分離を前提にカテゴリ化する(
*-ops-bot/*-deploy-bot/*-admin-bot/*-notify-bot) - 本番影響が大きい操作は専用 App に分離する
- Organization permissions は必要最小限(Members: Read など使うもののみ付与)
Variables / Secrets の原則
- Visibility は
Selected repositoriesを基本とし、アクセス元リポジトリを明示する - 秘密鍵
.pemはパスワードマネージャで管理し、貼り付け後にクリップボードを掃除する - Secret の rename は不可なので、命名は最初から将来の運用を見据える
Ruleset 設計の原則
- default branch を守る Ruleset の
bypass_actorsは空にする - 全 branch 対象の Ruleset を作る場合は、自動化 bot 用の
bypass_actorsを必ず設計する - bypass は App 単位(
actor_type: Integration)で行う - bypass_mode は用途に応じて選択する(直接 push を伴う自動化は
always)
Team Reviewer の原則
- 新規リポジトリ追加時のチェックリストに「配信対象 team を collaborator に追加」を入れる
- GitHub App の Organization permissions: Members: Read を付与する
- Permission は既存 team 設定と揃える(新 team を作らない限り admin に統一)
まとめ
-
.githubリポジトリ +repo-file-sync-action+ GitHub App で dependabot.yml の一括配信基盤を構築した - 設計の要は GitHub App の用途別分離方針・Variables / Secrets の可視性最小化・Ruleset の bypass_actors 設計・Team Reviewer のための3層権限
- 仕様把握は README だけでなく
action.ymlとsrc/まで照合する運用を標準化した - 配信基盤ができたことで、リポジトリ追加時の dependabot 設定漏れが構造的に起きない仕組みになった
今回使った BetaHuhn/repo-file-sync-action は CODEOWNERS や .github/workflows/* など他のファイル配信にも使えます。Dependabot 以外のガバナンス系ファイル配信にも横展開が可能です。
参考: 設定まとめ
| 設定項目 | 値 |
|---|---|
dependabot.yml の interval
|
weekly |
cooldown.default-days |
7 |
groups |
github-actions: "*" でまとめる |
sync-config.yml |
配信先リポジトリを列挙 |
| GitHub App permissions (Repository) | Contents: Write / Pull requests: Write |
| GitHub App permissions (Organization) | Members: Read |
| Variables visibility | Selected repositories (.github のみ) |
| Secrets visibility | Selected repositories (.github のみ) |
Ruleset bypass_actors
|
Team(admin)+ App(actor_type: Integration) |
| Action の SHA pin | 必須 |