macOSでNode.js/Pythonプロセスを常駐化する — LaunchAgent vs pm2 を実務視点で比較

益子 竜与志
益子 竜与志
XThreads
最終更新日:2026年03月28日公開日:2026年03月28日

「Node.jsのバッチ処理を定期的に動かしたいけど、tmuxで放置するのはちょっと…」「Pythonの監視スクリプトをMacにログインしたら勝手に立ち上がるようにしたい」——こんな場面、macOSで開発していると意外と出てきませんか? Linux本番環境ならsystemdに任せるところですが、macOSにはmacOS流の常駐化手段があります。本記事では、OS標準のLaunchAgentとNode.jsエコシステムで定番のpm2を、実務で使い分けるための判断基準とともに解説します。

プロセスの常駐化が必要になる場面

開発マシンとして使っているMacで「プロセスを常駐化したい」と感じるケースは、思ったより多いです。

  • ヘルスチェックスクリプトを毎週自動実行したい
  • ローカル開発用のAPIサーバーをログイン時に自動起動したい
  • ファイル監視 → 自動ビルドのデーモンを走らせておきたい
  • Slack BotやDiscord Botをローカルで常時起動しておきたい
  • IoTデータ収集スクリプトをMac miniで24時間回したい

Linux環境ならsystemdで .service ファイルを書くのが鉄板ですが、macOSではsystemdは使えません。代わりにmacOSが提供するのが launchd というプロセス管理システムで、ユーザー単位で使える LaunchAgent がまさにこの用途にぴったりはまります。

macOS LaunchAgentの基礎知識

macOS LaunchAgentのアーキテクチャ図解

LaunchAgentはmacOSの launchd というOS標準のプロセス管理機構の一部です。Appleが公式に提供している仕組みなので、追加のパッケージインストールは一切不要。macOSを使っている時点で、すでに使える状態になっています。

仕組みはシンプルで、 ~/Library/LaunchAgents/ ディレクトリにXML形式の .plist ファイルを配置し、 launchctl コマンドで登録するだけです。

LaunchAgentにはいくつかの動作モードがあります。

設定キー

動作

ユースケース

RunAtLoad: true

ログイン時に自動起動

常駐デーモン

KeepAlive: true

クラッシュ時に自動再起動

落ちたら困るプロセス

StartInterval: N

N秒ごとに定期実行

ポーリング処理

StartCalendarInterval

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>

各キーの役割を押さえておきましょう。

キー

説明

Label

一意の識別子。逆ドメイン形式が慣習

ProgramArguments

実行コマンド。配列の最初がバイナリ、以降が引数

RunAtLoad

ログイン時に自動起動

KeepAlive

プロセス終了時に自動再起動

WorkingDirectory

カレントディレクトリの指定

EnvironmentVariables

環境変数の設定

StandardOutPath / StandardErrorPath

標準出力・エラー出力のログファイル

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の比較図

ここからが本題です。両者を実務で使い分けるための比較表をまとめました。

観点

LaunchAgent

pm2

追加依存

なし(OS標準)

npm i -g pm2 が必要

対象言語

なんでもOK(Node/Python/Go/シェル…)

主にNode.js(他言語も一応可能)

クラスターモード

なし

あり(-i max

ゼロダウンタイムリロード

なし

あり(pm2 reload

ログ管理

自分でパス指定(ローテーションなし)

組み込み(pm2 logs、logrotate連携)

リアルタイム監視

なし

pm2 monit / pm2.io ダッシュボード

OS再起動後の復帰

標準動作(RunAtLoad)

pm2 startup + pm2 save が必要

定期実行(cron的)

StartCalendarInterval で可能

別途crontab等が必要

設定形式

XML(plist)

JSON / JS / CLI引数

ポータビリティ

macOSのみ

Linux / macOS / Windows

デプロイ連携

なし

pm2 deploy あり

学習コスト

低い(plistのキーを覚えるだけ)

低い(CLIベースで直感的)

ユースケース別の使い分け

比較表を見ても「で、結局どっち?」となると思うので、具体的なユースケース別に整理します。

LaunchAgentを選ぶべきケース

  • Mac上で1〜2個のプロセスを常駐させるだけ → 余計な依存を増やす理由がない
  • Node.js以外(Python、Go、シェルスクリプト等)も統一的に管理したい
  • 定期実行(週次バッチ、日次集計等)もまとめて管理したい
  • Mac miniをサーバー的に運用していて、OS標準の安定性を重視する
  • 開発ツール(ファイルウォッチャー、ローカルプロキシ等)の自動起動

pm2を選ぶべきケース

  • Node.jsアプリの本番運用(クラスターモード、ゼロダウンタイムリロードが欲しい)
  • Linux本番サーバーと同じ運用フローで統一したい
  • 複数のNodeプロセスをまとめて管理・監視したい
  • pm2.io のダッシュボードでチームメンバーとプロセス状況を共有したい
  • デプロイ自動化(pm2 deploy)と連携させたい

ざっくり言うと、macOSローカルのユーティリティ → LaunchAgentNode.jsアプリの本番運用 → pm2 という切り分けがシンプルでわかりやすいかなと思います。

ハマりポイントと対策

LaunchAgent設定時のよくあるハマりポイント

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特化レイヤー」という関係です。

どちらも設定は数分で終わるので、まずは小さなスクリプトで試してみてください。一度動くと「もっと早くやればよかった」と感じるはずです。

IT/DXプロジェクト推進するPMO・コンサル人材を提供しています

AI利活用×高生産性のリソースで、あらゆるIT/DXプロジェクトを一気通貫支援します

詳しく見る →
AI駆動型ITコンサルティング
Careerバナーconsultingバナー