Electronビルドをリリースし、リリースページが公開された直後、コーヒーマシンが一杯淹れ終える前に最初のサポートチケットが届きます。あるユーザーは「アプリがアップデートを一度も見つけられない」と言い、別のユーザーは「ダウンロードはできたがインストールできない」と訴えます。さらに別のユーザーは、認証フローが壊れた古いバイナリをまだ使い続けており、一方でログには役立つ情報がほとんど残っていません。
これが、 Electronアプリの自動アップデートにおける厄介な現実です。アップデータであるAPIは、あくまで一つの構成要素に過ぎません。プロダクションリリースは、プラットフォーム署名、トランスポートポリシー、マニフェスト、ホスティング、ライフサイクルイベント、オブザーバビリティ、ロールアウト制御、ロールバック経路にも依存します。これらを一つでもオプション扱いすれば、日常的なパッチが一夜にして重大インシデントになり得ます。
目次
- このガイドのきっかけとなった、午前2時のアップデート障害
- 適切なElectronアップデータの選び方
- メインプロセスにおける自動アップデートフローの実装
- 署名付きリリースとマニフェストのCI/CD構築
- ロールアウト、Channel、ロールバック戦略
- 自動アップデートをセキュリティ制御として扱う
- 本番アップデートのランブックとチェックリスト
このガイドのきっかけとなった、午前2時のアップデート障害
リリースはCIを通過し、一見いつも通りに見えました。しかし金曜の夜遅く、ある開発者が署名のないElectron buildをプッシュし、publishジョブが十分なアセットをアップロードしたことで、リリースが完了したように見えました。テスト環境ではアプリが起動しましたが、インストール済みの本番ビルドからアップデートパスを試した人は誰もいませんでした。
午前2時8分、PagerDutyがオンコールエンジニアにアラートを送りました。一部の端末で新しい認証フローが失敗し、アップデートを受け取ったユーザーはサインインを完了できませんでした。一方、他のユーザーはアップデーターがアーティファクトを検証・インストールできなかったため、以前のバージョンのままでした。一部の顧客には壊れたリリースが届き、残りのユーザーは明確な説明もないまま別バージョンを実行していました。
調査では、以下の5つの項目を確認しました:
- リリースフィードの確認。 バイナリは存在していましたが、期待されるメタデータだけでは、どのクライアントに配信すべきかが明確に定まっていませんでした。マニフェストはリリースパイプラインとインストール済みクライアントの間の「契約」であり、オプションのアップロード詳細ではありません。
- 署名の検証。 リリースが正しく署名されていなかったため、対象のプラットフォームで検証に失敗していました。署名が欠落している、あるいは無効である場合は、パブリッシュをブロックする必要があります。
- クライアントログの比較。 アップデートエラーは中央テレメトリに届かず、アプリがイベントを握りつぶして動作を続けたため、チームには信頼できる証拠が残りませんでした。
- ロールアウト制御を確認してください。 内部チャネルや段階的なコホートはありませんでした。対象となるすべてのクライアントが同じフィードを使っていたため、封じ込めポイントがないまま障害が広がりました。
- ロールバックを探してください。 チームには、以前のバージョンを再公開したり、クライアントを壊れたリリースから遠ざけたりするための、テスト済みの手順がありませんでした。
Electronの公式ドキュメントには、プラットフォームの制限が明記されています。 Linuxには自動アップデーターの組み込みサポートがありません。、またmacOSのアップデートリクエストは App Transport Securityの要件を満たす必要があります。。ドキュメントでは、署名をmacOSの信頼できるアップデートとリリース検証の前提条件として特定しています。 Electron autoUpdaterドキュメント はAPIの制約を定義しており、リリースシステムは周囲の運用管理を強制する必要があります。
ポストモーテムの教訓: 何が起きたかを説明できないアップデータは、テレメトリが欠落したリモートインストール試行です。
コストはエンジニアリング時間を超えて広がりました。顧客はデスクトップクライアントへの信頼を失い、サポートは不整合な動作を説明しなければならず、チームはインシデント前に存在すべきだったリリースプロセスを翌営業日に再構築するのに費やしました。
自動アップデートを 運用システム署名はリリースのゲート、マニフェストはクライアントとの契約を定義し、ロールアウトチャネルは影響範囲を限定します。ロールバックは緊急時の即興ではなく、テスト済みの経路として残ります。
適切なElectronアップデータの選び方
午前2時、誤ったアップデータ選択は運用上の問題になります。ネイティブバイナリの更新では、署名、マニフェスト、インストーラー、ロールバックを処理する必要があります。レンダラー層のJavaScriptやCSSの変更は別の経路をたどります。ホスティングの制約も重要です。小規模なGitHubホスティングプロジェクトに、エンタープライズ向け配布サービスと同じリリース管理は不要です。
electron-builder で署名済みアーティファクトを公開しているプロジェクトでは、 electron-updater が実用的なデフォルトです。そのエコシステムは、公開ターゲット、リリースマニフェスト、アーティファクトのダウンロード、次回起動時のインストールをカバーします。複数のホスティングモデルをサポートしますが、署名、フィードの可用性、チャネルポリシー、ロールアウト制御、監視はチームが担う必要があります。 Capgo 向け Electron アップデータ統合 は、ネイティブリリースと並行して Web レイヤーのバンドルを配信するハイブリッドモデルを検討する際に関連します。
update-electron-app は、GitHub Releases 周りに小さな統合を求めるチームに適しています。起動時と定期的な間隔でチェックするため、セットアップはシンプルですが、高度なチャネル選択、段階的なトラフィック制御、カスタムロールバックルールの余地は限られます。GitHub Releases とその可用性が運用要件に合うなら、小規模なリリースプロセスには妥当な選択肢です。
| オプション | ホスティング制御 | 署名サポート | チャネルと段階的ロールアウト | メンテナンス負荷 |
|---|---|---|---|---|
| electron-updater | S3、GitHub、汎用 HTTPS などの公開先 | パッケージ化されたリリース署名と統合 | 堅牢な基盤。カスタムポリシーは通常フィード周辺に実装 | 中程度 |
| update-electron-app | 主にシンプルな GitHub Releases ワークフロー | Electron の署名モデルを使用 | 周辺サービスを追加しない限り限定的 | 低 |
| Squirrel.Windows または Squirrel.Mac | プラットフォーム別の配布フロー | プラットフォームの署名要件に依存 | 可能だが、通常は追加のリリース基盤が必要 | レガシーアプリでは中程度 |
| カスタムサービス | マニフェスト、認可、コホート、フィードを完全に制御 | 検証設計と鍵管理は自社で担当 | 最大限の柔軟性 | 高 |
| Capgo Live Updates | Webレイヤーbundleのマネージド配信 | 独自のupdaterと配信モデルを使用 | オーディエンスターゲティングとchannelベースの配信 | ネイティブバイナリ更新とは運用モデルを分離 |
リリース承認、テナント指定、監査記録、規制されたデプロイ規則などによって実装コストが見合うなら、Hazel、Nuts、社内フィードのようなカスタムサービスが適しています。トレードオフは運用責任が続くことです。チームはマニフェストのセマンティクスを定義し、署名鍵を保護し、クライアント互換性を維持し、ダウンロード失敗・リリース拒否・ロールバックの挙動をテストする必要があります。
Live updates はネイティブシェルを再ビルドせずに、レンダラーのみの変更を配信できます。Electron、ネイティブモジュール、権限、インストーラーの挙動が変更される場合、バイナリ更新の代わりにはなりません。 独自のロールアウトロジックが必要でない限り、electron-updaterを使用してください。 カスタムロジックが必要な場合は、ダウンロードや差分更新の挙動をゼロから作り直すのではなく、既存のマニフェストやアーティファクトの規約に沿って構築してください。信頼できる道筋は、緊急時でもチームが監視し、段階的に展開し、元に戻せるものです。
メインプロセスにおける自動アップデートフローの実装
アップデートの確認とインストールはメインプロセスが担当すべきです。レンダラーはステータスを表示できますが、実行ファイルのアップデートを信頼するかどうかや、アプリを終了させるタイミングを決めるべきではありません。
まず publish target を設定する
最小限の electron-builder 設定は以下のようになります:
{
"build": {
"appId": "com.example.desktop",
"publish": [
{
"provider": "s3",
"bucket": "example-electron-releases",
"channel": "stable"
}
],
"nsis": {
"oneClick": false,
"allowToChangeInstallationDirectory": true
}
}
}
beta と stable のフィードは分けてください。チャンネルはリリースポリシーであり、UI 上のラベルではありません。各チャンネルは、正しい署名済みアーティファクトとマニフェストに解決される必要があります。
チェックのスケジュール設定とライフサイクルイベントの公開
呼び出しを checkForUpdates() 起動時のみに限定するのは、本番環境でよくある間違いです。ユーザーはアプリを数日間開いたままにすることもあるため、メインプロセスには制御されたインターバルと、オフライン動作を考慮したリトライ戦略が必要です。
const { app, BrowserWindow, ipcMain } = require('electron');
const { autoUpdater } = require('electron-updater');
let mainWindow;
let isQuitting = false;
let retryDelay = 60 * 1000;
function sendUpdateStatus(status, payload = {}) {
if (mainWindow && !mainWindow.isDestroyed()) {
mainWindow.webContents.send('update-status', { status, ...payload });
}
}
function scheduleUpdateCheck() {
setTimeout(async () => {
try {
await autoUpdater.checkForUpdates();
retryDelay = 60 * 1000;
} catch (error) {
sendUpdateStatus('error', { message: error.message });
retryDelay = Math.min(retryDelay * 2, 30 * 60 * 1000);
}
scheduleUpdateCheck();
}, retryDelay);
}
app.whenReady().then(() => {
mainWindow = new BrowserWindow({
webPreferences: {
preload: require('path').join(__dirname, 'preload.js')
}
});
autoUpdater.autoDownload = true;
autoUpdater.autoInstallOnAppQuit = false;
autoUpdater.on('checking-for-update', () => {
sendUpdateStatus('checking');
});
autoUpdater.on('update-available', info => {
sendUpdateStatus('available', { version: info.version });
});
autoUpdater.on('download-progress', progress => {
sendUpdateStatus('progress', { percent: progress.percent });
});
autoUpdater.on('update-downloaded', info => {
sendUpdateStatus('downloaded', { version: info.version });
});
autoUpdater.on('error', error => {
sendUpdateStatus('error', { message: error.message });
});
autoUpdater.checkForUpdates().catch(error => {
sendUpdateStatus('error', { message: error.message });
});
scheduleUpdateCheck();
});
ipcMain.handle('install-update', () => {
isQuitting = true;
autoUpdater.quitAndInstall(false, true);
});
app.on('before-quit', event => {
if (!isQuitting) {
return;
}
});
アップデートイベントの正確な挙動はプラットフォームやパッケージング構成によって異なるため、開発モードではなくインストール済みのアーティファクトでテストしてください。Electron のドキュメントでも、Windows の Squirrel 初回起動時など、起動タイミングに関する注意点が挙げられています。必要なプラットフォーム固有の初期化が完了する前に、アップデートチェックを実行しないでください。
処理をブロックせずにレンダラーへ状態を伝える
preload ブリッジは、限定的な API のみを公開してください:
const { contextBridge, ipcRenderer } = require('electron');
contextBridge.exposeInMainWorld('updates', {
onStatus(callback) {
ipcRenderer.on('update-status', (_event, status) => callback(status));
},
install() {
return ipcRenderer.invoke('install-update');
}
});
レンダラー側のプログレスバーは、あえてシンプルなままで構いません:
window.updates.onStatus(status => {
const progress = document.querySelector('#update-progress');
const message = document.querySelector('#update-message');
if (status.status === 'progress') {
progress.hidden = false;
progress.value = status.percent;
message.textContent = `Downloading update, ${Math.round(status.percent)}%`;
}
if (status.status === 'downloaded') {
message.textContent = `Version ${status.version} is ready to install`;
}
if (status.status === 'error') {
message.textContent = 'The update could not be downloaded. We will retry later.';
}
});
アプリケーションに即時再起動が必要な強い理由がない限り、本番環境ではユーザーの同意を得てからインストールするようにしましょう。設定してください。 isQuitting フラグを quitAndInstall()の前に。そうしないと、通常のウィンドウ終了ハンドラーがインストーラーへの引き継ぎを妨げる可能性があります。

2つの失敗パターンには、明示的なテストが必要です。1つ目は、すでに実行中のクライアントが checkForUpdates() 定期的に、起動時だけでなく呼び出す必要があることです。2つ目は、 error イベントがログとテレメトリに届く必要があることです。アプリが報告せずに握りつぶしてしまうと、あなたの アプリのトラブルシューティングのワークフローが エビデンスではなく推測から始まってしまいます。
署名付きリリースとマニフェストのCI/CD構築
リリースパイプラインこそが、ユーザーが何をインストールするかの正となる情報源です。ある開発者のマシンで動くローカルビルドがあっても、公開されたバイナリ、マニフェスト、署名、チャンネルがすべて同じリリースを指している証明にはなりません。
electron-builderのパブリッシュモデルでは、リリースのメタデータとアップデート対象が一緒に扱われることが前提です。多くの構成では、それは次のようなアーティファクトを意味します。 latest.yml Windows用、および latest-mac.yml macOS用のプラットフォーム固有パッケージとblockmapファイルも併せて。マニフェストがないと、完全に有効なバイナリでもクライアントから見えなくなります。
CIで署名を明示化する
簡略化したGitHub Actionsのパターンは以下の通りです:
name: release
on:
push:
tags:
- "v*"
jobs:
build:
strategy:
matrix:
os: [macos-latest, windows-latest]
runs-on: ${{ matrix.os }}
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: 24
cache: npm
- run: npm ci
- run: npm run test
- run: npm run build
- name: Build and publish
shell: bash
env:
CSC_LINK: ${{ secrets.CSC_LINK }}
CSC_KEY_PASSWORD: ${{ secrets.CSC_KEY_PASSWORD }}
WIN_CSC_LINK: ${{ secrets.WIN_CSC_LINK }}
AWS_ACCESS_KEY_ID: ${{ secrets.AWS_ACCESS_KEY_ID }}
AWS_SECRET_ACCESS_KEY: ${{ secrets.AWS_SECRET_ACCESS_KEY }}
run: npx electron-builder --publish always
プラットフォーム固有のSecretを使用し、署名用の素材はリポジトリ外に保管してください。署名に失敗したら、手動でアップロードされる未署名のフォールバックを生成するのではなく、ジョブを停止させるべきです。
| 変数 | 目的 |
|---|---|
CSC_LINK |
macOSの証明書または証明書のリファレンス |
CSC_KEY_PASSWORD |
macOSの署名用素材のパスワード |
WIN_CSC_LINK |
Windowsの証明書または証明書のリファレンス |
AWS_ACCESS_KEY_ID |
アクセス範囲を絞ったPublish credential |
AWS_SECRET_ACCESS_KEY |
Publish credentialとペアになるSecret |
publish設定では、プロバイダーとチャンネルを一貫して指定する必要があります:
{
"build": {
"publish": {
"provider": "s3",
"bucket": "example-electron-releases",
"channel": "stable",
"publishAutoUpdate": true,
"updaterCacheDirName": "example-desktop-updater"
}
}
}
公開前に、パッケージのバージョン、タグ、コミットSHA、スモークテスト結果を条件にジョブをゲートします。公開後は、フィードに期待どおりのマニフェストが含まれ、そのマニフェストが当該ジョブで生成された正確なアーティファクトを指していることを確認します。 CI設定ガイド これは、そうしたチェックを正式化するときに役立ち、パイプラインのオーケストレーションを比較しているチームにも、以下を理解するうえで役立つでしょう: JenkinsとAnsibleを併用すべきタイミング.
不完全なリリースをよく露呈させるコマンドは、あえて地味です:
npx electron-builder --publish never
test -f dist/latest.yml
test -f dist/latest-mac.yml
find dist -name "*.blockmap" -print
これらのチェックは、署名付きインストールテストの代わりにはなりません。ただし、クライアントが検出に必要とするメタデータなしでバイナリをアップロードするという運用ミスは捉えられます。
ロールアウト、Channel、ロールバック戦略
リリースフィードは、ダウンロードフォルダよりもデプロイ先として振る舞うべきです。 内部テスト, ベータや 最新 各チャンネルを分離し、それぞれに独自のマニフェストと署名済みアーティファクトセットを持たせます。プロモーションは、テスト済みのリリースをポリシー間で移動するものであり、クライアントがダウンロード中にファイルを上書きするものではありません。
チャンネルの分離は、本番環境への誤ったテストビルドの混入を防ぎます。フィードを評価する前に、アップデータはクライアントが内部コホート、ベータオーディエンス、または安定版ユーザーのいずれに属しているかを知っておくべきです。
広範囲への展開前にコホートを活用する
カスタムマニフェストフィールドで段階的配信を表現できます:
version: 4.8.0
path: Example-Setup-4.8.0.exe
sha512: signed-artifact-hash
rolloutPercentage: 10
メインプロセスでユーザーごとに安定したバケットを割り当て、そのバケットを rolloutPercentageと比較します。安定した割り当てが重要です。チェックのたびに適格と不適格の状態を行き来するユーザーは、予測不能な挙動を受け取り、サポート報告の解釈を困難にします。
リリースが観察期間を無事に終えた後にのみ、コホートを拡大してください。正確な期間は使用パターンに応じて設定すべきですが、判断はカレンダーだけでなくシグナルに基づいて行うべきです。アップデートチェックの結果、ダウンロード完了、起動の健全性、クラッシュ、レンダラー例外、認証の成功を追跡してください。
| シグナル | アクション | 理由 |
|---|---|---|
| フィードまたは署名エラーがチームの承認上限を超えた場合 | ロールアウトを保留 | クライアントがリリースを検証または検出できない可能性があります |
| アップデート後の起動チェックに失敗 | フィードを戻す | バイナリはインストールできても、起動時に失敗する可能性があります |
| プロモーション後にRendererの例外が増加 | 現在のコホートで保留 | ネイティブインストーラーは正常でも、新しいアプリケーション code は正常でない可能性があります |
| シグナルがリリース予算内に収まっている | コホートを拡大 | 拡大展開を裏付けるエビデンスがある |
ロールバックとアーティファクトの削除を混同しないでください。既存のクライアントにはキャッシュされたメタデータがあり、すでに問題のあるバージョンを実行している場合もあります。ロールバックプランには、以前の署名済みリリース、フィードの変更、そしてクライアント側で復旧できる挙動が必要です。
運用ルール: ロールバックは、インシデント発生時にアプリケーションを再ビルドすることなく、オンコール担当エンジニアが実行できなければなりません。
実務では、ランブックで以前のリリースマニフェストを影響を受けたチャネルに戻し、ステージングマーカーを無効化して、新しいチェックが安全なバージョンに解決されることを確認する必要があります。問題がネイティブシェルではなくレンダラーの code にある場合は、Webレイヤーに絞ったロールバックの方が速いことがあります。例えば、 Capgo のフェーズドロールアウト のようなプラットフォームは、その別個の配信レイヤーでは有効かもしれませんが、ネイティブバイナリのロールバックとWebバンドルのロールバックの境界を曖昧にすべきではありません。
自動アップデートをセキュリティ制御として扱う
Electronのアップデーターは、実行可能な code をダウンロードし、ユーザーの操作をほとんど必要とせずにインストールできます。そのため、アップデートパスは セキュリティ境界であり、単なる便利機能ではありません。Electronの公式ドキュメントでは、macOS ATSなどのプラットフォーム制約が説明されています。また、セキュリティ関連の報道では、アップデート基盤を制御する攻撃者が、code の署名チェックを通過する悪意のあるパッケージを配信できた2022年の事例が報じられています。詳細は次のドキュメントで説明されています: Electron Builderのauto-updateセキュリティドキュメント.
Code の署名は基盤であり続けますが、信頼モデルのすべてではありません。すべてのリリースに署名し、CI 中に証明書と署名 ID を検証し、文書化した鍵ローテーション手順を維持してください。macOS では署名に公証と、アプリケーションに適した Hardened Runtime を組み合わせてください。Windows では証明書の所有権、更新、ビルドアクセスを監査可能にしてください。Linux では Electron が汎用アップデーターを組み込んでいないため、ディストリビューション固有の戦略が必要です。
バイナリと同様にメタデータも保護する
メタデータチャネルが侵害されたり設定を誤ったりすると、署名済みバイナリでも誤ったリリースに関連付けられる可能性があります。アプリに埋め込んだ公開鍵で検証するマニフェスト署名を追加し、許可する最小バージョンを強制し、承認されたリカバリパスが明示的に許可しない限り予期しないダウングレードを拒否することを検討してください。
フィードにも本番環境向けの制御が必要です:
- 公開アクセスを制限: CI には、リリースアセットの公開に必要な権限だけを付与してください。
- 署名シークレットの保護: 証明書や秘密鍵はリポジトリ内のファイルではなく、管理されたシークレットストレージに保管してください。
- 依存関係の固定: CI で Electron、electron-builder、推移的依存関係をロックしてください。
- アーティファクトのレビュー: 生成されたパッケージをスキャンし、想定したコミットおよびバージョンと比較してください。
- セキュアなトランスポートを必須にする: 更新リクエストでは、ATSと厳格なHTTPS要件に従います。
- 検証失敗を監視する: 繰り返し発生する署名やマニフェストの失敗は、通常のネットワークノイズではなくセキュリティイベントとして扱います。
Electronのメンテナンスされているツールエコシステムは、パッケージングとアップデーターの対応範囲を拡大し続けていますが、メンテナンスがあっても脅威モデリングの必要性がなくなるわけではありません。実務上の目標は、バケット、CDN、ビルドステップのいずれかを侵害した攻撃者でも、クライアントに未承認のリリースを受け入れさせられないようにすることです。 署名検証ガイダンス は、その追加の検証レイヤーを設計する際に役立つ文脈を提供します。

本番アップデートのランブックとチェックリスト
リリースが準備完了とみなされるのは、別のエンジニアがプレッシャー下でも操作できる場合のみです。チェックリストはデプロイジョブとインシデントチャネルの近くに置いておきましょう。
リリース前ゲート
- バージョンの特定: パッケージバージョン、リリースタグ、コミットSHA、変更履歴が一致していることを確認します。
- 署名: すべてのプラットフォームの成果物が署名済みであり、ノタリゼーションまたは同等の検証が完了していることを確認します。
- マニフェスト契約: 確認
latest.yml,latest-mac.ymlハッシュ、パス、ブロックマップがアップロードされた成果物と一致すること。 - チャネルの安全性: リリースチャネルに昇格する前に、内部またはベータフィードに公開します。
- テレメトリ: アップデートチェック、ダウンロード進捗、インストール完了、起動時の健全性、エラーが受信されていることを確認します。
カナリアリリースとフルロールアウト
- コホート制御: まずは、意図的に規模を絞った社内ユーザーやベータ版ユーザーから開始しましょう。
- ヘルスバジェット: 起動失敗、アップデートのダウンロード失敗、レンダラー例外、または認証失敗がチームの承認した上限を超えた場合は、プロモートを保留してください。
- プロモート承認: リリースを安定版フィードに移行する前に、明示的なGo/No-Go判断を必須にします。
- 顧客への影響: サポート向けのメッセージは、最初のインシデント後ではなく、広く配布する前に用意しておきましょう。
インシデント対応
新しいバイナリが起動しない、プロセス生成が失敗する、またはアップデートのダウンロードが完了しない場合は、直ちにプロモートを停止してください。以前の署名済みマニフェストを復元し、ステージングマーカーを無効化して、新規クライアントが以前のバージョンに解決されることを確認します。その後、テレメトリでフリート全体が復旧しつつあることを確認してから、終息を通知してください。
正確なロールバックコマンドはプロバイダーによって異なりますが、手順は常にドキュメント化しておくべきです。 チャンネルのマニフェストを前のバージョンに切り替え、ステージングタグを無効化し、復旧に必要なら強制アップデートマーカーをプッシュし、ライブテレメトリでダウングレードパスを確認します。テストされていないロールバックは、単なる希望に過ぎません。

Capgo は、署名済みの Web レイヤー変更の配信、ターゲットチャンネル、ロールアウト制御、アップデートのオブザーバビリティを、レンダラー変更のたびにネイティブシェルを再ビルドせずに実現する Electron アップデーターを提供します。ネイティブバイナリのリリースと、制御された JavaScript および CSS の配信を分離したい場合は、こちらをご覧ください: Capgo 既存の Electron リリースパイプラインと併せて評価してください。