GitHub Actions と GitLab CI の比較(バックエンド・Web・モバイル向け)
GitHub Actions と GitLab CI をモノレポで比較:ランナー構成、シークレットの扱い、キャッシュ、バックエンド・Web・モバイル向けの実践的なパイプラインパターン。

マルチアプリ CI で人々が悩むこと
1つのリポジトリでバックエンド、Web、モバイルをビルドすると、CI は単に「テストを実行する」以上の役割になります。異なるツールチェーン、異なるビルド時間、異なるリリースルールを調整する交通整理役です。
最も一般的な課題は単純です:小さな変更があまりに多くの作業を引き起こすこと。ドキュメントの編集で iOS の署名が走ったり、バックエンドの細かな修正で Web の完全再ビルドが走ったりすると、マージごとに遅くてリスクが高く感じられます。
マルチアプリ構成では、早くからいくつかの問題が現れます:
- ランナードリフト:マシンごとに SDK バージョンが違い、CI とローカルでビルド挙動が異なる。\n- シークレットの拡散:API キー、署名証明書、ストア資格情報がジョブや環境に重複して保存される。\n- キャッシュの混乱:誤ったキャッシュキーで古いビルドが使われる、あるいはキャッシュがないと遅くなる。\n- 混在するリリースルール:バックエンドは頻繁デプロイが欲しい一方で、モバイルはゲートが厳しく追加のチェックが必要。\n- パイプラインの可読性:設定がジョブの壁のように成長して誰も触りたがらなくなる。
だからこそ、GitHub Actions と GitLab CI の選択は単一アプリよりモノレポで重要になります。パスで仕事を分ける方法、アーティファクトを安全に共有する方法、並列ジョブが互いに干渉しないようにする方法が必要です。
実務的な比較は主に4点に集約されます:ランナーのセットアップとスケーリング、シークレットの保管とスコープ、キャッシュとアーティファクト、そして「変更があったものだけをビルドする」を壊れやすいルールにせず表現する難易度です。
ここで扱うのは日々の信頼性と保守性についてで、どちらが統合が多いかや UI がきれいかといった話ではありません。また、Gradle、Xcode、Docker といったビルドツールの選択を置き換えるものでもありません。クリーンな構造を保ちやすい CI を選ぶ手助けです。
GitHub Actions と GitLab CI の構造
最大の違いは、各プラットフォームがパイプラインと再利用をどう整理するかで、これはバックエンド、Web、モバイルが同じリポジトリを共有し始めると重要になります。
GitHub Actions は自動化を .github/workflows/ 下の YAML ファイルに保存します。ワークフローは push、プルリクエスト、スケジュール、手動実行などのイベントでトリガーされます。GitLab CI はリポジトリルートの .gitlab-ci.yml を中心に、必要に応じてファイルを include して使い、パイプラインは通常 push、マージリクエスト、スケジュール、手動ジョブで動きます。
GitLab はステージ(build、test、deploy など)を中心に設計されています。ステージを定義してジョブをステージに割り当て、順番に実行します。GitHub Actions はワークフロー内にジョブがあり、ジョブはデフォルトで並列実行され、依存関係が必要なら明示します。
多くのターゲットに同じロジックを走らせる場合、GitHub のマトリクスビルドは自然な適合を示します(iOS と Android、複数の Node バージョンなど)。GitLab でも parallel ジョブや変数を使って同等のファンアウトは可能ですが、手作業で結線する部分が増えることが多いです。
再利用の見え方も異なります。GitHub では再利用可能なワークフローやコンポジットアクションをよく使います。GitLab では include、共有テンプレート、YAML アンカーや extends で再利用することが多いです。
承認や保護された環境の扱いも違います。GitHub は保護された環境と必要なレビュアー、環境シークレットを使って本番デプロイを停止させることが多いです。GitLab は保護されたブランチ/タグ、保護環境、手動ジョブを組み合わせて、特定のロールだけがデプロイできるようにするのが一般的です。
ランナーのセットアップとジョブ実行
ランナーのセットアップは、日常の使い勝手で両者が異なると感じ始めるポイントです。どちらもホスティッドランナー(マシンを管理しない)かセルフホストランナー(マシンを自前で管理)でジョブを実行できます。ホスティッドは始めやすく、セルフホストは速度や特殊ツール、プライベートネットワークへのアクセスが必要なときに選ばれます。
多くのチームで実用的な分け方は、バックエンドと Web は Linux ランナー、iOS は macOS ランナーのみで実行することです。Android は Linux で動きますが重めなのでランナーのサイズとディスク容量が重要です。
ホスティッドとセルフホスト:何を管理するか
ホスティッドランナーはメンテナンス不要で予測可能なセットアップを提供します。セルフホストは特定の Java/Xcode バージョン、高速なキャッシュ、内部ネットワークアクセスが必要な場合に理にかなっています。
セルフホストにするなら、早めにランナーロールを定義しましょう。多くのリポジトリは次のような少数セットで十分です:一般的な Linux ランナー(バックエンド/ウェブ用)、Android 用に大きめの Linux ランナー、iOS のパッケージング/署名用 macOS ランナー、権限を厳しくしたデプロイ専用ランナー。
ジョブごとに適切なランナーを選ぶ
両システムともランナーをターゲティングできます(GitHub はラベル、GitLab はタグ)。linux-docker、android、macos-xcode15 のようにワークロードに紐づく名前にしておくと管理しやすいです。
多くのフレークなビルドは分離不足から来ます。残ったファイル、壊れた共有キャッシュ、セルフホストに手で入れたツールがランダムな失敗を招きます。クリーンなワークスペース、固定したツールバージョン、定期的なランナーのクリーンアップが効果的です。
キャパシティと権限も繰り返しの課題です。特に macOS の可用性とコストが問題になります。一般的なデフォルトは:ビルドランナーはビルドできる、デプロイランナーはデプロイだけ、そして本番資格情報は最小限のジョブにだけ置く、です。
シークレットと環境変数
シークレットはマルチアプリ CI で最も危険になりやすい部分です。基本は似ています(プラットフォームにシークレットを保存し、実行時に注入する)が、スコーピングの扱いが違います。
GitHub Actions は通常リポジトリと組織レベルでシークレットを管理し、Environment レイヤーも使えます。Environment は本番で手動ゲートやステージングと別の値を使いたいときに便利です。
GitLab CI はプロジェクト、グループ、インスタンスレベルの CI/CD 変数を使います。環境スコープ変数や、保護(protected)やマスク(masked)などの保護もサポートします。複数チームが1つのモノレポを使う場合にこれらは有用です。
失敗の主な原因は偶発的な露出です:デバッグ出力、変数をエコーする失敗コマンド、あるいはアーティファクトに誤って設定ファイルが含まれるなど。ログとアーティファクトはデフォルトで共有されるものとして扱い、慎重に設計してください。
バックエンド+Web+モバイルのパイプラインでのシークレットは通常、クラウド資格情報、データベース URL、サードパーティ API キー、署名素材(iOS 証明書/プロファイル、Android キーストアとパスワード)、レジストリトークン(npm、Maven、CocoaPods)、自動化トークン(メール/SMS プロバイダ、チャットボット)などです。
複数環境(dev、staging、prod)がある場合は名前を一貫させ、ジョブをコピーする代わりに環境スコープで値を切り替えてください。これでローテーションとアクセス制御が管理しやすくなります。
いくつかのルールでほとんどの事故を防げます:
- 短命な資格情報(可能なら OIDC 経由でクラウドプロバイダ)を長寿命キーより優先する。\n- 最小権限を使う:バックエンド、Web、モバイルでデプロイ用の識別を分ける。\n- シークレットはマスクし、環境変数を出力しない(失敗時も含めて)。\n- 本番シークレットは保護されたブランチ/タグと必須レビュアーに限定する。\n- シークレットをビルドアーティファクトに保存しない(たとえ一時的でも)。
高インパクトで簡単な例:モバイルジョブはタグ付きリリースのときだけ署名用シークレットを受け取り、バックエンドのデプロイジョブは main マージ時に限定したデプロイトークンを使う。これだけで misconfig の被害範囲は大きく減ります。
高速化のためのキャッシュとアーティファクト
遅いパイプラインの多くは単純な理由で遅い:同じものを何度もダウンロードして再ビルドしている。キャッシュは繰り返し作業を避け、アーティファクトは特定の実行からの正確な出力を保持します。
何をキャッシュするかはビルド対象によります。バックエンドは依存関係やコンパイラキャッシュ(例:Go モジュールキャッシュ)が恩恵を受けます。Web はパッケージマネージャやビルドツールのキャッシュが効きます。モバイルは Linux だと Gradle と Android SDK のキャッシュ、macOS だと CocoaPods や Swift Package Manager のキャッシュが必要です。iOS の DerivedData のような攻撃的なビルド出力キャッシュはトレードオフを理解してから使いましょう。
両プラットフォームの基本パターンは同じ:ジョブの開始時にキャッシュを復元し、終了時に更新キャッシュを保存します。日常的な差分は制御性です。GitLab はキャッシュとアーティファクトの動作を1ファイルで明示でき、有効期限も扱いやすいです。GitHub Actions は別のアクションでキャッシュを扱うことが多く柔軟ですが、誤設定しやすい面があります。
モノレポではキャッシュキーがさらに重要になります。良いキーは入力が変わったときにだけ変化し、そうでないときは安定します。ロックファイル(go.sum、pnpm-lock.yaml、yarn.lock など)でキーを駆動し、リポジトリ全体ではなくビルドする特定のアプリフォルダのハッシュを含めるとよいです。またアプリごとに別キャッシュを保ち、ある変更で全てが無効にならないようにします。
保持したい成果物(リリースバンドル、APK/IPA、テストレポート、カバレッジファイル、ビルドメタデータ)はアーティファクトにしてください。キャッシュは速度向上のベストエフォート、アーティファクトは記録です。
それでも遅い場合は、巨大すぎるキャッシュ、毎回変わるキー(タイムスタンプやコミット SHA)、ランナー間で再利用できないキャッシュされたビルド出力がないか確認しましょう。
モノレポ適合:混乱しない複数パイプライン
モノレポが混乱するのは、すべての push がバックエンドのテスト、Web のビルド、モバイルの署名を引き起こすときです。クリーンなパターンは「変更を検出して、関係するジョブだけを実行する」ことです。
GitHub Actions ではアプリごとに別ワークフローを作り、パスフィルタでその領域のファイルが変わったときだけ実行することが多いです。GitLab CI では1つのパイプラインファイルで rules:changes を使うか、子パイプラインを作り、パスに基づいてジョブグループを作成/スキップします。
共有パッケージは信頼の崩壊ポイントです。packages/auth が変わると、フォルダ自体が直接変わっていなくてもバックエンドと Web の両方を再ビルドする必要が出ます。共有パスは複数パイプラインのトリガーとして扱い、依存境界を明確にしてください。
驚きを減らすシンプルなトリガーマップ例:
- バックエンドジョブは
backend/**またはpackages/**の変更で走る。\n- Web ジョブはweb/**またはpackages/**の変更で走る。\n- モバイルジョブはmobile/**またはpackages/**の変更で走る。\n- ドキュメントのみの変更は速いチェック(フォーマット、スペルチェック)だけを走らせる。
並列化できるものは並列化(ユニットテスト、リント、Web ビルド)し、制御が必要なものは直列化(デプロイ、アプリストア公開)します。GitLab の needs や GitHub のジョブ依存は、早めに速いチェックを走らせて失敗時に残りを止めるのに役立ちます。
モバイル署名は日常の CI から隔離してください。署名キーを専用環境の手動承認の背後に置き、タグ付きリリースや保護ブランチでのみ署名を実行します。通常のプルリクでは署名なしのアプリをビルドして検証できます。
ステップバイステップ:バックエンド、Web、モバイルのクリーンなパイプライン
クリーンなマルチアプリパイプラインは、意図が明確に分かる命名から始まります。1つのパターンを選んで守れば、ログを見れば何が動いたかわかるようになります。
読みやすいスキームの一例:
- パイプライン:
pr-checks、main-build、release\n- 環境:dev、staging、prod\n- アーティファクト:backend-api、web-bundle、mobile-debug、mobile-release
そこからジョブは小さく保ち、前のチェックに合格したものだけを昇格させます。
-
PR チェック(全プルリク): 変更があったアプリだけの速いテストとリントを実行します。バックエンドはデプロイ可能なアーティファクト(コンテナイメージやサーバーバンドル)をビルドし、後続ステップのために保存して再ビルドを避けます。
-
Web ビルド(PR + main): Web アプリを静的バンドルにビルドします。PR では出力をアーティファクトにするかプレビュー環境へデプロイします。main では
dev/staging向けのバージョン付けバンドルを生成します。 -
モバイル デバッグビルド(PR のみ): デバッグ APK/IPA をビルドします。リリース署名は行いません。目的は素早いフィードバックとテスターがインストールできるファイルを提供することです。
-
リリースビルド(タグのみ):
v1.4.0のようなタグが付いたら、フルのバックエンド・Web ビルドと署名済みモバイルリリースビルドを走らせ、ストア向け出力とリリースノートをアーティファクトとして保管します。 -
手動承認:
stagingからprodの間に承認を置き、基本的なテストの前ではなく後に置きます。開発者はビルドをトリガーできても、本番にデプロイして本番シークレットにアクセスできるのは承認されたロールだけです。
時間を浪費するよくあるミス
チームはワークフロー習慣のせいで数週間を無駄にすることがあります。
一つの罠は共有ランナーへの過度な依存です。多くのプロジェクトが同じプールを競合すると、ランダムなタイムアウト、遅いジョブ、ピーク時だけ失敗するモバイルビルドが発生します。バックエンド、Web、モバイルが重要なら、重いジョブは専用ランナー(あるいは少なくとも別キュー)で隔離し、リソース上限を明確にしておきましょう。
シークレットも時間の泥です。モバイル署名キーは扱いを誤りやすい。広範囲に保存してしまったり、冗長なログで漏らしたりするミスが多いです。署名素材は保護ブランチ/タグに限定し、シークレット値を出力するステップは避けてください。
キャッシュも巨大ディレクトリをキャッシュしたり、キャッシュとアーティファクトを混同したりすると裏目に出ます。安定した入力だけをキャッシュし、後で必要な出力はアーティファクトにしてください。
最後に、モノレポで全ての変更に対して毎回パイプラインを起動すると時間が無駄になります。README をちょっと編集しただけで iOS、Android、バックエンド、Web を全部再ビルドするようなら、CI に対する信頼が失われます。
役立つチェックリスト:
- パスベースのルールで影響範囲だけを走らせる。\n- テストジョブとデプロイジョブを分ける。\n- 署名キーはリリースワークフローに限定する。\n- 小さく安定した入力だけをキャッシュし、全ビルドフォルダはキャッシュしない。\n- 重いモバイルビルド向けに予測可能なランナー容量を計画する。
どちらのプラットフォームにするか決める前のクイックチェック
選ぶ前に実際の働き方を反映したチェックをいくつかやってください。モバイルビルド、複数環境、リリースが増えると、一つのアプリでは問題にならない選択が痛手になります。
注目すべき点:
- ランナープラン:ホスティッド、セルフホスト、または混在。iOS が絡むと混在に傾きやすいです。\n- シークレットプラン:シークレットの保管場所、誰が読めるか、ローテーション方法。本番はステージングより厳しく。\n- キャッシュプラン:何をキャッシュするか、どこに置くか、キーはどう作るか。キーが毎回変わるなら速度は出ません。\n- モノレポプラン:パスフィルタと、共通ステップ(リント、テスト)をコピー&ペーストなしで共有する方法。\n- リリースプラン:タグ、承認、環境の分離。誰が本番へ昇格できるかと、そのために必要な証拠を明示する。
これらの回答を短いシナリオでプレッシャーテストしてください。Go バックエンド、Vue Web、2 つのモバイルアプリがあるモノレポでは:ドキュメントだけの変更はほとんど何もしない;バックエンド変更はバックエンドテストと API アーティファクト作成;モバイル UI 変更は Android と iOS のみをビルド、のように説明できるかです。
1ページでそのフロー(トリガー、キャッシュ、シークレット、承認)が説明できなければ、同じリポジトリで両方のプラットフォームを1週間パイロットして、無難で予測可能に感じる方を選んでください。
例:現実的なモノレポのビルドとリリースフロー
1 つのリポジトリに backend/(Go)、web/(Vue)、mobile/(iOS と Android)の3フォルダがあるとします。
日常は素早いフィードバックが欲しく、リリース時はフルビルド、署名、公開ステップを走らせたいです。
実用的な分け方:
- フィーチャーブランチ:変更箇所のリントとユニットテスト、バックエンドと Web のビルド、必要なら Android のデバッグビルド。iOS は本当に必要なときだけ。\n- リリースタグ:すべてを実行し、バージョン付きアーティファクトを作成し、モバイルを署名してリリースストレージへアップロード。
モバイルが入るとランナー選択は変わりがちです。Go と Vue のビルドはほとんどどこでも Linux で問題ありません。iOS は macOS ランナーが必要で、これが多くの場合選択を左右します。チームでマシンを完全に制御したいなら、セルフホストランナーのフリート運用が GitLab CI の方がやりやすいことがあります。手間を減らしてすぐ始めたいなら GitHub のホスティッドランナーが便利ですが、macOS の分数や可用性を計画に入れてください。
キャッシュで実際に時間を節約できますが、最適なキャッシュはアプリごとに異なります。Go はモジュールダウンロードとビルドキャッシュを、Vue はパッケージマネージャのキャッシュとロックファイル変化のみで再ビルドするように、モバイルは Linux で Gradle と Android SDK、macOS で CocoaPods/SwiftPM をキャッシュし、大きなキャッシュと失効を想定してください。
実践的な決定ルール:コードが既にどちらかのプラットフォームにホストされているならまずはそこから始め、ランナー(特に macOS)、権限、コンプライアンスが問題なら移行を検討してください。
次のステップ:選び、標準化して、安全に自動化する
コードと人が既にいるツールを選ぶのが現実的です。違いは日常の摩擦(レビュー、権限、壊れたビルドの診断の速さ)として現れます。
シンプルに始めましょう:アプリごとに1つのパイプライン(バックエンド、Web、モバイル)。安定してきたら共通ステップを再利用テンプレートに移してコピー&ペーストを減らしつつ所有権を曖昧にしないでください。
シークレットのスコープはオフィスの鍵の管理書と同じように書き残してください。本番シークレットをすべてのブランチが読めるようにしてはいけません。ローテーションのリマインダーを設定し(四半期ごとがゼロよりは良い)、緊急失効の方法を合意しておきます。
もしノーコードジェネレータを使って実際のソースコードが生成されるなら、生成/エクスポートを第一級の CI ステップと見なしてください。例えば AppMaster(appmaster.io)は Go バックエンド、Vue3 Web、Kotlin/SwiftUI のモバイルを生成するので、パイプラインで生成を実行し、生成後に実際に変わったターゲットだけをビルドする運用が可能です。
チームが信頼するフローを作ったら、新しいリポジトリのデフォルトにしておき、退屈であることを目標にしてください:トリガーが明確、ランナーが予測可能、シークレットが厳格、リリースは意図したときだけ実行される、という状態です。
よくある質問
まずはコードとチームが既にいるプラットフォームをデフォルトにし、macOS ランナー、権限、コンプライアンスが理由でない限り移行を急がないのが現実的です。日常的に影響するのはランナーの可用性、シークレットのスコーピング、そして「変更があったものだけをビルドする」を壊れやすいルールにせず表現できるかどうかです。
GitHub Actions はセットアップが素早く、マトリクスビルドが自然に使える一方、ワークフローが複数の YAML ファイルに分かれる傾向があります。GitLab CI はステージ中心で一元管理しやすく、パイプラインが大きくなったときにキャッシュやアーティファクト、ジョブ順序を一か所で制御しやすいという違いがあります。
macOS は希少資源と考え、本当に iOS のパッケージングや署名が必要なときだけ使います。一般的な基準は、バックエンドと Web に Linux ランナー、Android 用により強力な Linux ランナー、iOS 用に macOS ランナーを用意し、デプロイ用のランナーは権限を絞ることです。
SDK やツールのバージョンがマシンごとに異なると同じジョブが違う挙動になるのがランナードリフトです。ツールのバージョンを固定し、セルフホストのランナーで手作業インストールを避け、クリーンなワークスペースを使い、定期的にランナーイメージをクリーンアップすれば大抵改善します。
必要なジョブの最小集合にのみシークレットを渡し、本番用シークレットは保護されたブランチ/タグと承認の背後に置くことが基本です。モバイルでは署名素材をタグ付きリリース時のみ注入し、プルリクエストでは署名なしのデバッグビルドで検証するのが安全です。
キャッシュは繰り返し作業を高速化するため、アーティファクトは特定の実行からの正確な成果物を保持するために使います。キャッシュはベストエフォートで置き換わる可能性があるのに対し、アーティファクトは保存して追跡したいビルドバンドルやテストレポート、APK/IPA のような成果物です。
ロックファイル(go.sum、pnpm-lock.yaml、yarn.lock 等)のような安定した入力を基にキーを作り、ビルドするリポジトリの一部にスコープして、無関係な変更で全部を無効にしないようにします。タイムスタンプやコミット SHA のように毎回変わるキーは避け、アプリごとに分けたキャッシュを維持してください。
パスベースのルールを使って、ドキュメントや無関係なフォルダの変更が高コストなジョブを起動しないようにします。共有パッケージはトリガーとして明示的に扱い、どのターゲットが再ビルドされるかを予測可能にしておきます。
署名キーとストア資格情報は通常の CI 実行から分離し、タグや保護ブランチ、承認の背後に隠します。プルリクエストではリリース署名なしのデバッグバリアントをビルドし、高リスクな認証情報を日常的なジョブに漏らさないようにします。
できます。生成工程を第一級のステップとして扱い、入力と出力を明確にしてキャッシュや再実行が予測可能になるようにします。AppMaster のようなツール(appmaster.io)で実際のソースコードが生成される場合は、関連する変更で再生成してから、実際に生成後に変わった箇所だけをビルドするのがきれいな方法です。


