プロセスの常駐化が必要になる場面
開発マシンとして使っているMacで「プロセスを常駐化したい」と感じるケースは、思ったより多いです。
- ヘルスチェックスクリプトを毎週自動実行したい
- ローカル開発用のAPIサーバーをログイン時に自動起動したい
- ファイル監視 → 自動ビルドのデーモンを走らせておきたい
- Slack BotやDiscord Botをローカルで常時起動しておきたい
- IoTデータ収集スクリプトをMac miniで24時間回したい
Linux環境ならsystemdで .service ファイルを書くのが鉄板ですが、macOSではsystemdは使えません。代わりにmacOSが提供するのが launchd というプロセス管理システムで、ユーザー単位で使える LaunchAgent がまさにこの用途にぴったりはまります。
macOS LaunchAgentの基礎知識

LaunchAgentはmacOSの launchd というOS標準のプロセス管理機構の一部です。Appleが公式に提供している仕組みなので、追加のパッケージインストールは一切不要。macOSを使っている時点で、すでに使える状態になっています。
仕組みはシンプルで、 ~/Library/LaunchAgents/ ディレクトリにXML形式の .plist ファイルを配置し、 launchctl コマンドで登録するだけです。
LaunchAgentにはいくつかの動作モードがあります。
設定キー | 動作 | ユースケース |
|---|---|---|
| ログイン時に自動起動 | 常駐デーモン |
| クラッシュ時に自動再起動 | 落ちたら困るプロセス |
| N秒ごとに定期実行 | ポーリング処理 |
| cron的なスケジュール実行 | 日次・週次バッチ |
つまり「常駐」も「定期実行」も、LaunchAgent一本でカバーできるわけです。crontabの代わりとしても使えますし、KeepAliveをつければプロセス監視+自動復旧までやってくれます。地味にすごいやつなんですよね。
実践:LaunchAgentでNode.jsプロセスを常駐化する
実際にNode.jsアプリケーションをLaunchAgentで常駐化してみます。例として、簡単なHTTPサーバーを自動起動する設定を作ります。
Step 1: Node.jsのフルパスを確認する
まず最初に、Node.jsバイナリのフルパスを調べます。これがLaunchAgent設定で 最も重要なポイント です。
which node
# 例: /Users/yourname/.local/share/mise/installs/node/24.14.0/bin/node
# nvm を使っている場合
# /Users/yourname/.nvm/versions/node/v20.11.0/bin/node
# Homebrewの場合
# /opt/homebrew/bin/node
LaunchAgentはシェルの PATH を引き継がないので、単に node と書いても動きません。ここでフルパスを取得しておかないと「設定したのに動かない」の沼にハマります。
Step 2: plistファイルを作成する
以下の内容で ~/Library/LaunchAgents/com.example.my-node-app.plist を作成します。
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN"
"http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>Label</key>
<string>com.example.my-node-app</string>
<key>ProgramArguments</key>
<array>
<string>/Users/yourname/.local/share/mise/installs/node/24.14.0/bin/node</string>
<string>/Users/yourname/projects/my-app/server.js</string>
</array>
<key>RunAtLoad</key>
<true/>
<key>KeepAlive</key>
<true/>
<key>WorkingDirectory</key>
<string>/Users/yourname/projects/my-app</string>
<key>EnvironmentVariables</key>
<dict>
<key>NODE_ENV</key>
<string>production</string>
<key>PORT</key>
<string>3000</string>
</dict>
<key>StandardOutPath</key>
<string>/tmp/my-node-app.stdout.log</string>
<key>StandardErrorPath</key>
<string>/tmp/my-node-app.stderr.log</string>
</dict>
</plist>
各キーの役割を押さえておきましょう。
キー | 説明 |
|---|---|
| 一意の識別子。逆ドメイン形式が慣習 |
| 実行コマンド。配列の最初がバイナリ、以降が引数 |
| ログイン時に自動起動 |
| プロセス終了時に自動再起動 |
| カレントディレクトリの指定 |
| 環境変数の設定 |
| 標準出力・エラー出力のログファイル |
Step 3: 登録と動作確認
# LaunchAgentを登録
launchctl load ~/Library/LaunchAgents/com.example.my-node-app.plist
# 動作確認
launchctl list | grep com.example
# 0 0 com.example.my-node-app
# ↑ PID Exit Label
# ログ確認
tail -f /tmp/my-node-app.stdout.log
launchctl list の出力で、最初の数字がPID(0以外なら起動中)、2番目がExitCode(0なら正常)です。
Step 4: 停止・再起動
# 停止(アンロード)
launchctl unload ~/Library/LaunchAgents/com.example.my-node-app.plist
# 設定変更後の再読み込み
launchctl unload ~/Library/LaunchAgents/com.example.my-node-app.plist
launchctl load ~/Library/LaunchAgents/com.example.my-node-app.plist
実践:LaunchAgentでPythonスクリプトを常駐化する
Pythonの場合もやり方は同じです。ファイル監視スクリプトを週次で実行する例を見てみます。
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN"
"http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>Label</key>
<string>com.example.weekly-report</string>
<key>ProgramArguments</key>
<array>
<string>/usr/bin/python3</string>
<string>/Users/yourname/scripts/weekly_report.py</string>
</array>
<key>StartCalendarInterval</key>
<dict>
<key>Weekday</key>
<integer>1</integer>
<key>Hour</key>
<integer>10</integer>
<key>Minute</key>
<integer>0</integer>
</dict>
<key>StandardOutPath</key>
<string>/tmp/weekly-report.stdout.log</string>
<key>StandardErrorPath</key>
<string>/tmp/weekly-report.stderr.log</string>
</dict>
</plist>
この例では StartCalendarInterval で毎週月曜の10:00に実行しています。 Weekday: 1 が月曜日(0=日曜〜6=土曜)です。crontabの書式を覚えなくてもXMLのキーで直感的に書けるのは地味にありがたいですね。
Pythonの場合、macOS標準の /usr/bin/python3 を使うか、pyenvやconda等で管理しているPythonを使うかでパスが変わるので、同様に which python3 でフルパスを確認してから設定してください。
pm2という選択肢
Node.jsのプロセス管理といえば pm2 が真っ先に思い浮かぶ方も多いのではないでしょうか。pm2はNode.js製のプロセスマネージャーで、npmからグローバルインストールして使います。
# インストール
npm install -g pm2
# アプリケーション起動
pm2 start app.js --name "my-app"
# クラスターモード(全CPUコアを活用)
pm2 start app.js -i max
# ステータス確認
pm2 status
# ログ確認
pm2 logs my-app
# リアルタイム監視
pm2 monit
pm2の強みは「Node.jsアプリの運用に特化した機能群」です。クラスターモードでCPU全コアを使い切ったり、 pm2 reload でゼロダウンタイムリロードができたり、 pm2 monit でCPU/メモリをリアルタイム監視できたり。Node.jsの本番運用で欲しい機能がひととおり揃っています。
OS再起動後の自動復帰もできますが、ちょっと手順が必要です。
# startup スクリプト生成(OS再起動後の自動復帰用)
pm2 startup
# → 表示されたコマンドを実行
# 現在のプロセスリストを保存
pm2 save
実は pm2 startup がmacOSで何をやっているかというと、内部的にLaunchAgentのplistを生成して登録しています。つまりpm2の自動復帰機能も、裏ではLaunchAgentの仕組みに乗っかっているんですね。
LaunchAgent vs pm2 — 徹底比較

ここからが本題です。両者を実務で使い分けるための比較表をまとめました。
観点 | LaunchAgent | pm2 |
|---|---|---|
追加依存 | なし(OS標準) |
|
対象言語 | なんでもOK(Node/Python/Go/シェル…) | 主にNode.js(他言語も一応可能) |
クラスターモード | なし | あり( |
ゼロダウンタイムリロード | なし | あり( |
ログ管理 | 自分でパス指定(ローテーションなし) | 組み込み( |
リアルタイム監視 | なし |
|
OS再起動後の復帰 | 標準動作(RunAtLoad) |
|
定期実行(cron的) | StartCalendarInterval で可能 | 別途crontab等が必要 |
設定形式 | XML(plist) | JSON / JS / CLI引数 |
ポータビリティ | macOSのみ | Linux / macOS / Windows |
デプロイ連携 | なし |
|
学習コスト | 低い(plistのキーを覚えるだけ) | 低い(CLIベースで直感的) |
ユースケース別の使い分け
比較表を見ても「で、結局どっち?」となると思うので、具体的なユースケース別に整理します。
LaunchAgentを選ぶべきケース
- Mac上で1〜2個のプロセスを常駐させるだけ → 余計な依存を増やす理由がない
- Node.js以外(Python、Go、シェルスクリプト等)も統一的に管理したい
- 定期実行(週次バッチ、日次集計等)もまとめて管理したい
- Mac miniをサーバー的に運用していて、OS標準の安定性を重視する
- 開発ツール(ファイルウォッチャー、ローカルプロキシ等)の自動起動
pm2を選ぶべきケース
- Node.jsアプリの本番運用(クラスターモード、ゼロダウンタイムリロードが欲しい)
- Linux本番サーバーと同じ運用フローで統一したい
- 複数のNodeプロセスをまとめて管理・監視したい
- pm2.io のダッシュボードでチームメンバーとプロセス状況を共有したい
- デプロイ自動化(pm2 deploy)と連携させたい
ざっくり言うと、macOSローカルのユーティリティ → LaunchAgent、Node.jsアプリの本番運用 → pm2 という切り分けがシンプルでわかりやすいかなと思います。
ハマりポイントと対策

LaunchAgentを使い始めると、ほぼ全員がハマるポイントがあります。先に知っておくだけで相当な時間を節約できるので、まとめておきます。
1. PATHが通らない問題
最もよくあるトラブルです。LaunchAgentはログインシェル(.zshrc 等)を読み込まないので、シェルで node と打てても、plistに node と書くと「command not found」になります。
# ❌ これは動かない
<string>node</string>
# ✅ フルパスで指定する
<string>/Users/yourname/.local/share/mise/installs/node/24.14.0/bin/node</string>
mise、nvm、nodenv、pyenv等のバージョン管理ツールを使っている場合、 which コマンドで表示されるパスをそのまま使えばOKです。
2. 起動即クラッシュのループ
KeepAlive: true を設定している場合、プロセスが起動してすぐにクラッシュすると「起動→クラッシュ→再起動→クラッシュ…」のループに入ります。macOSにはスロットリング機構があるので無限ループにはなりませんが、ログが肥大化する原因になります。
対策としては、まず KeepAlive なしで手動テストして、安定動作を確認してからKeepAliveを有効にする手順がおすすめです。
3. 環境変数が足りない
データベース接続情報やAPIキーを環境変数で渡している場合、plist内の EnvironmentVariables で明示的に指定する必要があります。 .env ファイルは自動で読まれません。
<key>EnvironmentVariables</key>
<dict>
<key>DATABASE_URL</key>
<string>postgresql://localhost:5432/mydb</string>
<key>API_KEY</key>
<string>your-api-key-here</string>
</dict>
あるいは、アプリ側で dotenv パッケージを使って .env ファイルを自前で読み込むようにしておけば、plistへの環境変数記述は最小限で済みます。
4. plistの文法エラー
XMLなので、タグの閉じ忘れや属性値の引用符ミスがあると読み込みに失敗します。作成後は plutil コマンドでバリデーションしましょう。
plutil -lint ~/Library/LaunchAgents/com.example.my-node-app.plist
# OK なら: com.example.my-node-app.plist: OK
5. 新しいlaunchctlコマンド体系
macOS 13 (Ventura) 以降、Appleは新しいlaunchctlサブコマンドを推奨しています。従来の load/unload も動作しますが、将来的には新コマンドへの移行が推奨されています。
# 新しいコマンド体系
launchctl bootstrap gui/$(id -u) ~/Library/LaunchAgents/com.example.my-node-app.plist
launchctl bootout gui/$(id -u)/com.example.my-node-app
# 従来のコマンド(まだ動く)
launchctl load ~/Library/LaunchAgents/com.example.my-node-app.plist
launchctl unload ~/Library/LaunchAgents/com.example.my-node-app.plist
まとめ
macOSでNode.jsやPythonのプロセスを常駐化する方法として、LaunchAgentとpm2を比較してきました。
個人的な使い分けの結論はシンプルです。macOSローカルで完結するユーティリティやバッチ処理にはLaunchAgent。OS標準で追加依存なし、定期実行も自動復旧もこれ一本で賄えます。Node.jsアプリの本番運用やクロスプラットフォーム対応が必要ならpm2。クラスターモードやゼロダウンタイムリロードなど、Node.js特化の運用機能が光ります。
そして面白いのが、pm2自体もmacOS上では内部的にLaunchAgentを使って自動復帰を実現していること。つまりLaunchAgentはmacOSにおけるプロセス常駐化の「土台」であり、pm2はその上に乗っかった「Node.js特化レイヤー」という関係です。
どちらも設定は数分で終わるので、まずは小さなスクリプトで試してみてください。一度動くと「もっと早くやればよかった」と感じるはずです。















