♻️

クラウド時代の逆張り:GitHub Actions を手元のM1 Maxに移して年19万円+月30時間を取り戻す

に公開

「クラウドCIにすべて任せる」が常識化した今、あえて手元のM1 Maxにビルドを戻したらどうなるか を実費ベースで試算した。

  • 3つのFlutterリポジトリのGitHub Actions使用状況を調査
  • 実請求CSVから集計すると 直近30日で $120.65(約18,940円) だった
  • うち macOSビルドが76%($92.13) を占める
  • M1 Max を self-hosted runner にすれば iOS + Android の両方をローカル化 でき、年間 約19万円のコスト削減
  • ビルド時間は 16〜18分 → 5〜7分(約1/3) に短縮、月30時間の待ち時間削減
  • 専用機(Mac mini M2 Pro 約20万円)を購入しても ROI 約1年 で十分採算が合う
  • 時間価値換算では 年約273万円の生産性向上ポテンシャル

背景

Flutter製モバイルアプリを2つ運営しており、それぞれが共通モジュール(submodule)を参照する構成。CI/CDはGitHub Actionsで回しているが、月間費用がいくらかかっているか、内訳まできちんと把握できていなかった。

そこで以下を整理した:

  1. 各リポジトリのワークフロー一覧
  2. 直近30日の実行回数・時間
  3. 実費用の内訳(公開料金ではなく実請求ベース)
  4. self-hosted runner への移行による削減ポテンシャル
  5. ビルド時間短縮による開発体験への影響

1. ワークフロー一覧の取得

gh CLI を使ってワークフローと実行履歴を取得する。

# ワークフロー一覧
gh api repos/<org>/<repo>/actions/workflows \
  --jq '.workflows[] | {id, name, state, path}'

# 直近の実行履歴(過去30日)
gh api "repos/<org>/<repo>/actions/runs?created=>=2026-04-03&per_page=100" \
  --paginate \
  --jq '.workflow_runs[] | [.name, .conclusion, .run_started_at, .updated_at] | @tsv'

2. 直近30日の実行統計

3リポジトリ合計で 609 runs / 5,702 分 の実行があった。

Repository A(メインアプリ)

ワークフロー runs 失敗率
Release Orchestrator 132 23%
CI Smoke Check 176 53%
Android Dev Build 1 -
Dependabot 4 75%

Repository B(姉妹アプリ)

ワークフロー runs 失敗率
Release Orchestrator 97 28%
CI Smoke Check 119 4%
create-asana-attachment 30 3%
その他 50 -

Repository C(共通モジュール)

ワークフロー runs
submodule-pr-trigger -
parent-build-result -
develop-parent-sync -

3. ジョブレベルでのOS別集計

Release Orchestrator は中で iOS(macOS)と Android(Linux)のreusable workflowを呼び出すため、ジョブの labels を見て分類する必要がある。

gh api "repos/<org>/<repo>/actions/runs/<run_id>/jobs" \
  --jq '.jobs[] | [.labels[0], .name, .started_at, .completed_at] | @tsv'

実行例の出力:

ubuntu-latest    bump            2026-05-02T15:12:12Z  2026-05-02T15:12:24Z
macos-15         ios/build-ios   2026-05-02T15:12:29Z  2026-05-02T15:29:09Z
ubuntu-latest    android/build   2026-05-02T15:12:27Z  2026-05-02T15:32:09Z

4. macos-15 の実体を確認する

macos-15 ラベルが Apple Silicon か Intel か、ジョブのログから判定できる。

job_id=<id>
gh api "repos/<org>/<repo>/actions/jobs/$job_id/logs" | grep -E "Image:|architecture:"

出力:

Image: macos-15-arm64
architecture: ARM64
[command]/opt/homebrew/bin/git ...   # Apple Silicon の Homebrew パス

macos-15 = Apple Silicon 3-core(料金は$0.08/min)

ラベル別の構成は以下:

ラベル 構成 公開料金
macos-15 Apple Silicon 3-core $0.08/min
macos-15-large Intel 12-core $0.16/min
macos-15-xlarge Apple Silicon 6-core $0.32/min

5. 実費用の取得(Usage Report CSV)

GitHub UI から CSV をダウンロードする。

https://github.com/organizations/<org>/settings/billing/usage
→ 「Get usage report」ボタン
→ 期間選択(直近30/90/180日)
→ メールで CSV のリンクが届く

CSV のカラム:

date, product, sku, quantity, unit_type,
applied_cost_per_quantity, gross_amount, discount_amount, net_amount,
username, organization, repository, workflow_path, cost_center_name

awk で集計:

awk -F',' '$12=="\"<repo>\"" {
  gsub(/"/,"",$3); gsub(/"/,"",$4); gsub(/"/,"",$7); gsub(/"/,"",$13);
  totalq[$13"|"$3] += $4;
  totalgross[$13"|"$3] += $7;
}
END {
  for (k in totalq) {
    split(k, p, "|");
    printf "%-50s %-25s %10.1f %10.4f\n", p[1], p[2], totalq[k], totalgross[k];
  }
}' usageReport.csv

6. 実費用の集計結果

単価(CSV applied_cost_per_quantity より)

公開料金より約25%安いレートが適用されていた(Enterprise契約割引と推測)。

SKU 公開料金 実レート
actions_linux (2-core) $0.008/min $0.006/min
actions_linux_4_core $0.016/min $0.012/min
actions_macos $0.08/min $0.062/min

リポジトリ別合計(直近30日)

リポジトリ 実行分 gross net
Repository A 3,372 $66.99 $64.71
Repository B 1,970 $51.50 $50.78
Repository C 360 $2.16 $2.10
合計 5,702 $120.65 $117.59

SKU別合計

SKU gross 構成比
actions_macos 1,486 $92.13 76%
actions_linux (2-core) 3,678 $22.07 18%
actions_linux_4_core 538 $6.45 5%

7. M1 Max への移行検討

macos-15 ランナーは Apple Silicon 3-core

つまり手元の M1 Max は GitHub の macos-15 とアーキテクチャ互換。self-hosted runner として登録すれば、iOS ビルドをローカルで動かせる。

Android ビルドも実は M1 Max でそのまま動く

最初は「Linux ジョブはDocker経由が必要」と考えたが、Android ビルドに限って言えばそれは過剰。Apple Silicon の macOS で完全ネイティブに動作する。

構成要素 M1 Max での動作
Gradle JVMベースでOS非依存
Android SDK / NDK / build-tools Google公式で macOS-arm64 版を配布
Kotlin / Java コンパイル ARM64ネイティブ
AGP(Android Gradle Plugin) macOS対応
Firebase CLI / fastlane macOSで日常使い
APK/AAB 署名 apksigner / jarsigner どちらも macOS で動作

runs-on: ubuntu-latestruns-on: [self-hosted, macOS, ARM64] に書き換えるだけで動く。

移行可能性の判定

カテゴリ 説明 M1 Max での実現方法
🟢 そのまま移行可 macOS native + Android Apple Silicon ARM64 でネイティブ実行
🟡 工夫して移行可 純Linux ジョブ Docker / Lima / UTM 経由
🔴 移行不可 GitHub-managed Dependabot等は対象外
区分 費用 全体比
🟢 そのまま移行可(iOS + Android) 3,364 $106.62 88%
🟡 工夫すれば移行可(Linux軽量ジョブ) 2,330 $13.97 12%
🔴 移行不可 8 $0.05 0%

推奨戦略:iOS + Android を移行

項目
移行対象 2リポジトリの iOS + Android ビルド
カバー時間 3,364分(全体の59%)
削減費用 $106.62/月
ビルド時間短縮 16〜18分 → 5〜7分(約1/3)
技術難度 低(Xcode / Android SDK 既存資産を流用)
維持コスト 低(Xcode/Flutter/Android SDK 更新のみ)

CI Smoke Check や Asana attachment のような短時間頻発ジョブはGitHub-hostedで放置するのが最適。並列実行リソースを self-hosted で奪い合わずに済む。

8. ROI計算

前提

項目
為替レート $1 = 157円(2026-05-03 時点)
電気代単価 36円/kWh
M1 Max 平均消費電力 アクティブ時 100W / アイドル時 30W
月次削減額 $106.62(約16,740円)

電気代の試算

iOS + Android ビルドの合計アクティブ稼働時間は 約53.8時間/月。残りはアイドル(Sleep禁止)。

状態 時間 消費電力 kWh
アクティブ 53.8h 100W 5.38
アイドル(Sleep禁止) 666.2h 30W 19.99
月合計 720h - 25.37 kWh

電気代: 25.37 × 36円 = 913円/月(約 $5.82)

年間正味削減

項目 月額 年額
GitHub Actions 削減 -$106.62(-16,740円) -$1,279(-200,880円)
電気代 +$5.82(+913円) +$70(+10,956円)
正味削減 -$100.80(-15,827円) -$1,209(-189,924円)

シナリオ別比較(5年スパン、運用工数は業務吸収)

シナリオ 機材投資 5年累計効果
A. 既存M1 Max共用 0円 +95万円
B. Mac mini M2 Pro 専用機 20万円 +75万円
C. Mac Studio M2 Max 専用機 30万円 +65万円

投資回収期間

ROI = 投資額 / 年間削減額

Mac mini M2 Pro:  20万円 / 19.0万円 = 1.05年
Mac Studio M2 Max: 30万円 / 19.0万円 = 1.58年

専用機を買っても 約1年でペイ。さらに5年使えば 65〜95万円の純益

9. ビルド時間短縮 — もう一つの大きなメリット

費用削減だけが移行のメリットではない。ビルド時間の短縮による開発体験の改善こそ最大の効果かもしれない。

スペック比較

項目 GitHub-hosted macos-15 GitHub-hosted ubuntu-4core M1 Max (16-inch MacBook Pro)
CPU Apple M1, 3 perf cores Linux 4 cores Apple M1 Max, 10 cores (8P+2E)
GPU 8コア - 24 or 32コア
メモリ帯域 ~68 GB/s ~25 GB/s 400 GB/s (約6〜16倍)
RAM 7GB(実効) 16GB 32 〜 64GB
ストレージ ~14GB SSD ~14GB SSD NVMe SSD(数TB)
キャッシュ ジョブごとに毎回ダウンロード 同上 永続キャッシュ可

実測ビルド時間(直近30日の平均)

CSV と jobs API から逆算した1ジョブあたりの所要時間:

ビルド種別 runner 1ジョブ平均 M1 Max移行後(推定)
iOS(Repository A) macos-15 12.6分 5〜7分
iOS(Repository B) macos-15 12.6分 5〜7分
Android(Repository A) ubuntu-2core 6.7分 2〜3分
Android(Repository B) ubuntu-4core 9.7分 3〜5分
Release Orchestrator全体(並列実行、最遅律速) - 16〜18分 5〜7分

なぜここまで速くなるのか

  1. コア数が3倍以上(macOS 3-core or Linux 4-core → 10-core)
    • Flutter の Dart kernel ビルド、Xcode の Swift コンパイル、Gradle の Kotlin コンパイルはマルチスレッド処理が中心
    • メモリ帯域も6〜16倍あるためボトルネックが解消
  2. キャッシュが永続化される
    • ~/.pub-cache, ~/Library/Developer/Xcode/DerivedData, ~/.gradle/caches, Pods/ がジョブをまたいで残る
    • GitHub-hosted では毎回 actions/cache でダウンロード(300MB〜1GB)が必要だが、self-hosted では不要
  3. Gradle daemon が常駐
    • Android ビルドでは Gradle daemon の起動だけで30秒〜1分かかる
    • self-hosted ならプロセスが常駐するので即時実行
  4. ネットワークレイテンシゼロ
    • submodule clone, gem install, pod install のダウンロードがローカルキャッシュ or 高速回線
  5. キューイング待機ゼロ
    • GitHub-hosted は混雑時にジョブがキューに数分待つことがある
    • self-hosted は専有なのでトリガー即実行

開発体験への影響

Before(GitHub-hosted、16〜18分/リリース)

PR push → CI開始まで30秒〜2分のキュー待ち
       → ビルド開始
       → 16〜18分待機(コーヒー1杯)
       → TestFlight + Firebase App Distribution upload完了

1日のリリース確認サイクル: 約20分
集中力の中断: 数回

After(M1 Max、5〜7分/リリース)

PR push → CI即開始(キューなし)
       → 5〜7分でビルド完了
       → upload完了

1日のリリース確認サイクル: 約8分
集中力の中断: ほぼなし(フロー維持可能)

月間の時間削減効果

項目 GitHub-hosted M1 Max 削減
iOS ビルド月間累計 24.8時間 11.5時間 -13.3h
Android ビルド月間累計 29.0時間 12.0時間 -17.0h
合計 53.8時間 23.5時間 -30.3h/月

エンジニアの実働時間として、月30時間以上の待ち時間が削減される。チーム全員で共有されるなら、その効果は人数倍に広がる。

副次効果

① デプロイ頻度の向上

  • ビルドが速いと「ちょっと修正してTestFlight投げる」のハードルが下がる
  • 結果として 1日あたりのリリース回数が増え、フィードバックサイクルが短縮

② Flaky テストの再実行コストが低い

  • ビルドが Flaky で再実行になっても、5分なら待てる
  • GitHub-hosted の16分待ちだと「もう放置でいいか」となりがち → 品質低下

③ デバッグ効率

  • ローカル M1 Max なので、ビルドが失敗したら ~/_work/ 配下に直接アクセスしてログ調査可能
  • GitHub Actions の Web UI でログを追うより圧倒的に早い

④ 並列化のポテンシャル

  • M1 Max は 10コアあるため、複数ジョブを並列実行可能
  • iOS と Android を1台で同時にビルドしても余裕

金銭価値換算

エンジニア時給を仮に 7,500円($50相当)とすると:

項目
月間削減時間 30.3時間
時間価値換算 7,500円 × 30.3 = 227,250円/月
年間 2,727,000円(約 $17,370)

費用削減(年 約19万円)よりもむしろ 時間価値の方が14倍以上大きい

10. 注意点

セキュリティ

  • private repo でもメンバーは workflow_dispatch などで任意コードを実行可能
  • 個人マシン共用だと secrets が個人環境に展開される
  • 専用ユーザー or 専用マシンでの分離が望ましい

運用負荷

  • Xcode / Flutter / Android SDK のバージョン更新は手動
  • DerivedData / pub_cache / Pod cache / Gradle cache の定期掃除が必要
  • マシン故障時の単一障害点リスク
  • Sleep禁止設定 + launchd 常駐化が必要

ディスク

  • 最低150GB、できれば200GB以上の空きを確保
  • iOS ビルド1回あたり 5〜10GB、Android ビルドは1〜3GB 生成される

Linux 固有の依存があれば書き換え必要

Android ビルドのワークフロー内に以下が含まれていれば調整が必要:

  • apt-get installbrew install に置き換え
  • bash スクリプトの GNU 拡張(sed -i の挙動差など)
  • Docker container action(macOS 上の Docker Desktop で動かすか、別アプローチ検討)

実際のプロジェクトでは Linux 固有処理は最小限で、ほぼ書き換え不要だった。

まとめ

  • GitHub Actions の費用を 実請求CSVベース で正確に把握すれば、削減ポイントは明確
  • macOS ビルドが圧倒的にコスト支配項(10倍単価)
  • M1 Max を self-hosted runner にすれば iOS と Android の両方 がローカル化可能
  • 既存マシン共用ならノーリスクで 年19万円のコスト削減
  • 専用機購入でも ROI 約1年で十分採算が合う
  • 更にビルド時間が16〜18分 → 5〜7分に短縮し、エンジニアの月30時間が浮く
  • 金銭価値換算では 時間短縮効果 (年約273万円) > 費用削減 (年約19万円)

クラウドCI全盛の時代に、あえてローカルに戻すという選択肢。Apple Silicon ネイティブ世代のMacの性能を考えると、これは「時代遅れ」ではなくむしろ 理にかなった逆張り だ。クラウドの利便性は維持しつつ、重い iOS / Android ビルドだけ手元に降ろすハイブリッド構成が、コスト・時間・開発体験のすべてを改善する現実解。


参考

dotD Tech Blog

Discussion