なぜセルフホストの Mac ランナーを使うのか?
GitHub ホスト型の macOS ランナーは便利ですが、分単位の課金のため、ビルド量が増えるほどコストも増えます。1 分あたり$0.062(GitHub 標準 macOS ランナーの料金、2026年10月時点)で、月に 75 時間のビルドを実行するチームは約$279を支払うことになります。GitHub はセルフホストランナーの利用時間に課金しないため、My Remote Mac の専用 Mac mini M4 は分単位の課金なしの定額で月額$85(USD・税抜)。同等(またはそれ以上)のハードウェアをわずかなコストで利用できます。
| 項目 | My Remote Mac セルフホスト | GitHub ホスト型ランナー |
|---|---|---|
| コスト | 月額$85(分単位の課金なし) | $0.062/分(75 時間で約$279/月) |
| アーキテクチャ | Apple M4、物理Macを1:1で専有 | Apple M1 の VM、3 コア、RAM 7 GB(標準ランナー) |
| ビルド速度 | 約 4 分(同じプロジェクト) | 約 12 分(中規模プロジェクト) |
| 永続的なキャッシュ | あり(永続的なディスク) | なし(使い捨て) |
| カスタムソフトウェア | 完全な root アクセス | 限定的 |
| 同時ジョブ数 | ランナー 1 つにつき 1 ジョブ。ランナーや Mac を追加して拡張 | 5(Free・Pro・Team)/ 50(Enterprise) |
My Remote Mac の料金は USD・税抜です。 GitHub の数値:標準 macOS ランナー 1 分あたり$0.062(GitHub の料金ドキュメント、2026年10月時点)。
主なメリット: ランナーが永続的であるため、DerivedData、SPM キャッシュ、CocoaPods がビルド間で保持されます。これだけでも、毎回ゼロから始まる使い捨ての GitHub ホスト型ランナーと比べて、ビルド時間を 50〜70% 短縮できます。
前提条件
始める前に、次のものが揃っていることを確認してください。
- My Remote Mac の専用 Mac mini M4 ビルドサーバー(月額$85から、USD・税抜)
- リポジトリまたは組織への管理者アクセス権を持つ GitHub アカウント
- Mac Mini への SSH アクセス(My Remote Mac のサブスクリプションに付属します)
- Apple Developer アカウント(コード署名とプロビジョニングプロファイル用)
- YAML と ターミナルコマンドの基本的な知識
ステップ1:Mac Mini に SSH 接続して Xcode をインストールする
まず、Mac Mini M4 に SSH で接続します。My Remote Mac サーバーをセットアップした際に認証情報を受け取っているはずです。
SSH で接続する
# Connect to your Mac Mini M4
ssh admin@your-server-ip
# Verify you're on Apple Silicon
uname -m
# Expected output: arm64
# Check the macOS version
sw_vers
Xcode Command Line Tools をインストールする
# Install Command Line Tools
xcode-select --install
# Accept the license agreement
sudo xcodebuild -license accept
# Verify installation
xcode-select -p
# /Library/Developer/CommandLineTools
Xcode(フルバージョン)をインストールする
iOS ビルドには Xcode アプリケーションの完全版が必要です。ヘッドレスサーバーにインストールする最も速い方法は xcodes を使うことです。
# Install Homebrew (if not already installed)
/bin/bash -c "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/HEAD/install.sh)"
# Add Homebrew to PATH
echo 'eval "$(/opt/homebrew/bin/brew shellenv)"' >> ~/.zprofile
eval "$(/opt/homebrew/bin/brew shellenv)"
# Install xcodes CLI tool
brew install xcodes
# List available Xcode versions
xcodes list
# Install the latest release of Xcode (asks for your Apple Account)
xcodes install --latest
# Show the installed versions and their paths
xcodes installed
# e.g. 27.0 (27A266a) /Applications/Xcode-27.0.0.app
# Set it as the active Xcode (interactive SSH session: sudo asks for your password)
sudo xcode-select -s /Applications/Xcode-27.0.0.app/Contents/Developer
# Verify
xcodebuild -version
# Xcode 27.0
# Build version 27A266a
iOS シミュレーターをインストールする
# Install the iOS simulator runtime that matches your Xcode
xcodebuild -downloadPlatform iOS
# Verify simulator availability
xcrun simctl list runtimes
xcrun simctl list devices available
ステップ2:GitHub Actions ランナーをインストールする
次に、GitHub Actions ランナーエージェントをダウンロードして設定します。GitHub 上でリポジトリに移動し、Settings > Actions > Runners > New self-hosted runner に進んで、macOS + ARM64 を選択します。
ダウンロードして設定する
# Create a directory for the runner
mkdir -p ~/actions-runner && cd ~/actions-runner
# Download the latest runner package (macOS ARM64). The same commands, with the
# current version and its checksum, are shown on Settings > Actions > Runners >
# New self-hosted runner
RUNNER_VERSION=$(curl -fsSL https://api.github.com/repos/actions/runner/releases/latest \
| sed -n 's/.*"tag_name": *"v\([^"]*\)".*/\1/p')
curl -o "actions-runner-osx-arm64-${RUNNER_VERSION}.tar.gz" -L \
"https://github.com/actions/runner/releases/download/v${RUNNER_VERSION}/actions-runner-osx-arm64-${RUNNER_VERSION}.tar.gz"
# Extract the package
tar xzf "actions-runner-osx-arm64-${RUNNER_VERSION}.tar.gz"
# Configure the runner
# Replace YOUR_TOKEN with the token from GitHub Settings
./config.sh --url https://github.com/YOUR_ORG/YOUR_REPO \
--token YOUR_TOKEN \
--name "mac-mini-m4-runner" \
--labels "self-hosted,macOS,ARM64,M4" \
--work "_work"
# Test the runner interactively first
./run.sh
永続的な launchd サービスとしてインストールする
エージェントを対話的に実行するのはテストには問題ありませんが、本番では自動的に開始し、クラッシュしても再起動する必要があります。GitHub は macOS 向けの組み込みサービスインストールスクリプトを提供しています。
# Install as a launchd service (a LaunchAgent; no sudo on macOS)
cd ~/actions-runner
./svc.sh install
# Start the service
./svc.sh start
# Check the service status
./svc.sh status
# View the launchd plist (for reference)
cat ~/Library/LaunchAgents/actions.runner.*.plist
これでサービスはランナー用ユーザーのログイン時に毎回起動し、プロセスが停止すればランナーを再起動します。LaunchAgent は Mac の起動時に単独では開始しません。ヘッドレスの Mac では、そのユーザーの自動ログインを有効にしてください(システム設定 > ユーザとグループ。FileVault がオンの間は利用できません)。これで再起動後もランナーがオンラインに戻ります。ランナーがリポジトリの Settings > Actions > Runners ページに「Idle」として表示されることを確認できます。
複数のリポジトリ向けにランナーを設定する(組織レベル)
# For an organization-level runner, use the organization URL:
./config.sh --url https://github.com/YOUR_ORG \
--token YOUR_ORG_TOKEN \
--name "mac-mini-m4-org-runner" \
--labels "self-hosted,macOS,ARM64,M4" \
--runnergroup "Default" \
--work "_work"
# This allows ALL repositories in your organization to use this runner
ステップ3:iOS ビルドワークフローを作成する
リポジトリ内の .github/workflows/ios-build.yml にワークフローファイルを作成します。このワークフローは、セルフホストの Mac Mini M4 ランナー上で実行されます。
name: iOS Build & Test
on:
push:
branches: [ main, develop ]
pull_request:
branches: [ main ]
jobs:
build:
runs-on: [self-hosted, macOS, ARM64, M4]
# bash -eo pipefail: a failing xcodebuild fails the step even when piped to xcbeautify
defaults:
run:
shell: bash
env:
SCHEME: "MyApp"
# Any device listed by: xcrun simctl list devices available
DESTINATION: "platform=iOS Simulator,name=iPhone 17,OS=latest"
# Pick the Xcode for this job without sudo (path from: xcodes installed)
DEVELOPER_DIR: /Applications/Xcode-27.0.0.app/Contents/Developer
# Caches sit next to the checkout: actions/checkout cleans the checkout
# (git clean -ffdx) on every run, but not its parent folder
DERIVED_DATA: ${{ github.workspace }}/../DerivedData
SPM_CACHE: ${{ github.workspace }}/../SourcePackages
steps:
- name: Checkout code
uses: actions/checkout@v7
- name: Show the selected Xcode
run: xcodebuild -version
- name: Resolve Swift Package Dependencies
run: |
xcodebuild -resolvePackageDependencies \
-scheme "$SCHEME" \
-clonedSourcePackagesDirPath "$SPM_CACHE"
- name: Install xcbeautify
run: command -v xcbeautify >/dev/null || brew install xcbeautify
- name: Build the app
run: |
xcodebuild build \
-scheme "$SCHEME" \
-destination "$DESTINATION" \
-clonedSourcePackagesDirPath "$SPM_CACHE" \
-derivedDataPath "$DERIVED_DATA" \
| xcbeautify
- name: Run unit tests
run: |
xcodebuild test \
-scheme "$SCHEME" \
-destination "$DESTINATION" \
-clonedSourcePackagesDirPath "$SPM_CACHE" \
-derivedDataPath "$DERIVED_DATA" \
-resultBundlePath TestResults.xcresult \
| xcbeautify
- name: Upload test results
if: always()
uses: actions/upload-artifact@v7
with:
name: test-results
path: TestResults.xcresult
リリースビルドのためのコード署名を追加する
TestFlight や App Store へのデプロイには、コード署名のステップを追加します。証明書とプロビジョニングプロファイルは GitHub Secrets として保存してください。
deploy:
needs: build
runs-on: [self-hosted, macOS, ARM64, M4]
if: github.ref == 'refs/heads/main'
env:
# Same Xcode as the build job (App Store Connect requires Xcode 26 or later)
DEVELOPER_DIR: /Applications/Xcode-27.0.0.app/Contents/Developer
steps:
- name: Checkout code
uses: actions/checkout@v7
- name: Install certificate and provisioning profile
env:
BUILD_CERTIFICATE_BASE64: ${{ secrets.BUILD_CERTIFICATE_BASE64 }}
P12_PASSWORD: ${{ secrets.P12_PASSWORD }}
BUILD_PROVISION_PROFILE_BASE64: ${{ secrets.BUILD_PROVISION_PROFILE_BASE64 }}
KEYCHAIN_PASSWORD: ${{ secrets.KEYCHAIN_PASSWORD }}
run: |
# Create a temporary keychain
KEYCHAIN_PATH=$RUNNER_TEMP/app-signing.keychain-db
security create-keychain -p "$KEYCHAIN_PASSWORD" $KEYCHAIN_PATH
security set-keychain-settings -lut 21600 $KEYCHAIN_PATH
security unlock-keychain -p "$KEYCHAIN_PASSWORD" $KEYCHAIN_PATH
# Import certificate
CERTIFICATE_PATH=$RUNNER_TEMP/build_certificate.p12
echo -n "$BUILD_CERTIFICATE_BASE64" | base64 --decode -o $CERTIFICATE_PATH
security import $CERTIFICATE_PATH -P "$P12_PASSWORD" \
-A -t cert -f pkcs12 -k $KEYCHAIN_PATH
security list-keychain -d user -s $KEYCHAIN_PATH
# Install provisioning profile (the folder Xcode 16 and later read)
PP_PATH=$RUNNER_TEMP/build_pp.mobileprovision
echo -n "$BUILD_PROVISION_PROFILE_BASE64" | base64 --decode -o $PP_PATH
mkdir -p ~/Library/Developer/Xcode/UserData/Provisioning\ Profiles
cp $PP_PATH ~/Library/Developer/Xcode/UserData/Provisioning\ Profiles
- name: Build for distribution
run: |
xcodebuild archive \
-scheme "MyApp" \
-archivePath DerivedData/MyApp.xcarchive \
-destination "generic/platform=iOS" \
CODE_SIGN_STYLE=Manual
- name: Export IPA
run: |
xcodebuild -exportArchive \
-archivePath DerivedData/MyApp.xcarchive \
-exportOptionsPlist ExportOptions.plist \
-exportPath DerivedData/Export
- name: Upload to TestFlight
env:
APP_STORE_CONNECT_API_KEY: ${{ secrets.APP_STORE_CONNECT_API_KEY }}
APP_STORE_CONNECT_ISSUER_ID: ${{ secrets.APP_STORE_CONNECT_ISSUER_ID }}
run: |
# altool reads the key from ~/.appstoreconnect/private_keys/AuthKey_<KEY_ID>.p8
xcrun altool --upload-app \
-f DerivedData/Export/MyApp.ipa \
-t ios \
--apiKey $APP_STORE_CONNECT_API_KEY \
--apiIssuer $APP_STORE_CONNECT_ISSUER_ID
# The runner is persistent: remove the signing files at the end of every job
- name: Clean up keychain and provisioning profile
if: ${{ always() }}
run: |
security delete-keychain $RUNNER_TEMP/app-signing.keychain-db || true
rm -f ~/Library/Developer/Xcode/UserData/Provisioning\ Profiles/build_pp.mobileprovision
ステップ4:パフォーマンスを最適化する
セルフホストランナーの最大の利点の 1 つは、永続的なキャッシュです。ここでは、Mac Mini M4 を最大限に活用するための主要な最適化を紹介します。
DerivedData のキャッシュを有効にする
ランナーが永続的であるため、DerivedData はビルド間で保持されます。一貫した DerivedData のパスを使用してください。
# In your workflow, keep DerivedData next to the checkout (job env, as above):
# DERIVED_DATA: ${{ github.workspace }}/../DerivedData
# ... -derivedDataPath "$DERIVED_DATA"
# Each repository then gets ~/actions-runner/_work/<repo>/DerivedData
# On the runner, remove the DerivedData of repositories not built for 7 days
# (cron job every Sunday at 03:00, keeps your existing crontab entries)
(crontab -l 2>/dev/null; echo '0 3 * * 0 find "$HOME"/actions-runner/_work/*/DerivedData -maxdepth 0 -type d -mtime +7 -exec rm -rf {} +') \
| crontab -
SPM パッケージをキャッシュする
# Use clonedSourcePackagesDirPath to keep SPM packages on disk
# (SPM_CACHE points outside the checkout, as in the workflow above)
xcodebuild build \
-scheme "MyApp" \
-clonedSourcePackagesDirPath "$SPM_CACHE" \
-derivedDataPath "$DERIVED_DATA"
# This avoids re-downloading packages on every build
テストの並列実行
# Run tests in parallel across multiple simulators
# (devices from: xcrun simctl list devices available)
set -o pipefail
xcodebuild test \
-scheme "MyApp" \
-destination "platform=iOS Simulator,name=iPhone 17,OS=latest" \
-destination "platform=iOS Simulator,name=iPhone 17 Pro,OS=latest" \
-parallel-testing-enabled YES \
-maximum-parallel-testing-workers 4 \
-derivedDataPath "$DERIVED_DATA" \
| xcbeautify
より見やすいログのために xcbeautify をインストールする
# xcbeautify formats Xcode output for CI environments
brew install xcbeautify
# Use it by piping xcodebuild output (pipefail keeps xcodebuild's exit status):
set -o pipefail && xcodebuild build -scheme "MyApp" | xcbeautify
よくある問題のトラブルシューティング
GitHub でランナーが「Offline」と表示される
これは通常、launchd サービスが実行されていないことを意味します。サービスの状態とログを確認してください。
# Check service status
./svc.sh status
# View logs
cat ~/actions-runner/_diag/Runner_*.log | tail -50
# Restart the service
./svc.sh stop
./svc.sh start
コード署名が「No signing certificate」で失敗する
launchd サービスは異なるユーザーコンテキストで実行されます。キーチェーンにアクセスできることを確認してください。
# Ensure the login keychain is unlocked for the runner user
security unlock-keychain -p "YOUR_PASSWORD" ~/Library/Keychains/login.keychain-db
# Or use a dedicated keychain in your workflow (recommended)
security create-keychain -p "$KEYCHAIN_PASSWORD" build.keychain
security default-keychain -s build.keychain
security unlock-keychain -p "$KEYCHAIN_PASSWORD" build.keychain
シミュレーターの起動に失敗する
シミュレーターは時々動かなくなることがあります。ビルド間でリセットしてください。
# Shutdown all running simulators
xcrun simctl shutdown all
# Erase all simulator data (nuclear option)
xcrun simctl erase all
# Boot a specific simulator
xcrun simctl boot "iPhone 17"
ディスク容量が不足してくる
Xcode のビルドは大量のデータを生成します。自動クリーンアップを設定してください。
# Clean old DerivedData
rm -rf ~/Library/Developer/Xcode/DerivedData/*
# Remove simulators whose runtime is no longer installed
xcrun simctl delete unavailable
# Clean Homebrew cache
brew cleanup --prune=7
# Remove old Xcode archives
rm -rf ~/Library/Developer/Xcode/Archives/*
コスト分析
さまざまなチーム規模とビルド量に対する詳細なコスト比較を示します。
| チーム規模 | My Remote Mac の費用 | 月間ビルド数 | GitHub ホスト型の費用 | 月間節約額 |
|---|---|---|---|---|
| 個人開発者 | 月額$85 | 100 ビルド(平均 10 分) | 月額$62 | なし(月額$23割高) |
| 小規模チーム(5 人) | 月額$85 | 500 ビルド(平均 10 分) | 月額$310 | 月額$225 |
| 中規模チーム(15 人) | 月額$229(M4 Pro) | 1500 ビルド(平均 10 分) | 月額$930 | 月額$701 |
| エンタープライズ(50 人以上) | 月額$458(M4 Pro 2 台) | 5000 ビルド以上 | 月額$3,100以上 | 月額$2,642以上 |
My Remote Mac の料金は USD・税抜です。 GitHub の数値:標準 macOS ランナー 1 分あたり$0.062(GitHub の料金ドキュメント、2026年10月時点)。
結論: 月に約 140 ビルドを超えるチームであれば、セルフホストの Mac Mini M4 はすぐに元が取れます。さらに、ウォームキャッシュを備えた永続的なハードウェアではビルドが速くなるため、チームの開発時間も節約できます。個人開発者の行のように月 100 ビルドの場合は、GitHub ホスト型ランナーのほうが月額$23安くなります。それでも個人開発者が専用 Mac を選ぶ理由はあります。キャッシュが永続的に保持され、ビルドが速く、分単位の課金もありません。リリースがたまにしかない場合は、日額プランでリリース日に Mac を 24 時間あたり$14から(USD・税抜)利用できます。自動更新をオンにしない限り 24 時間で停止し、同じ Mac のまま月額プランに切り替えることもできます。