nvm環境でのpm2設定方法 の変更点

更新


#author("2026-04-02T10:33:26+00:00","default:administrator","administrator")
#author("2026-04-02T11:03:48+00:00","default:administrator","administrator")
[[公開メモ]]

#contents

* nvm 環境で pm2 を使う際の推奨設定 [#acf74c51]

** pm2 の位置づけ [#r6a26401]

pm2 はプロセスマネージャーであり、''アプリケーションの外側でアプリを管理するもの''。
概念的には nginx や systemd と同じ層にいる。

ただし、ユーザーレベルの nvm と親和性があり、システムグローバル、というよりもユーザーグローバルな存在になっている。

ユーザーごとに pm2 デーモンが立ち上がり、そこで複数のプロジェクトの node サーバーを管理可能。

そのため、''pm2 は各プロジェクトの `package.json` に入れるのではなく、ユーザーグローバルにインストールする'' のがベストプラクティスとなる。

 OS 起動
   └── pm2(ユーザーグローバル)
         ├── プロジェクトA(node_modules に pm2 なし)
         ├── プロジェクトB(node_modules に pm2 なし)
         └── プロジェクトC(node_modules に pm2 なし)

** nvm 環境での推奨構成 [#uc576c85]

*** pm2 を起動する Node バージョンを固定する [#m2a98485]

nvm 環境ではプロジェクトごとに(実際にはフォルダー毎に)異なる node バージョンを使い分けられる

しかしユーザーレベルで見ると pm2 デーモンは同時に1つだけ存在しており、それと異なる node 上の pm2 コマンドを叩くとバージョンの不一致が生じてしまう。

つまり pm2 を呼び出す際には常に''「デーモンとして常駐する pm2 と同じ Node バージョン」''を使う必要がある。

そこで、''`nvm alias default` で固定された node バージョン'' にグローバルにインストールされた pm2 をユーザーグローバルな唯一の pm2 として利用するのが良い。

 LANG:console
 $ nvm alias default v24.14.1
 $ npm install -g pm2   # あるいは pnpm global add pm2

*** pm2 自身が使う Node とアプリの Node は独立している [#s86bbf41]

pm2 はアプリを子プロセスとして fork する。

起動する Node は `pm2.config.cjs` の `interpreter` で指定できる。

 LANG:js
 // pm2.config.cjs
 module.exports = {
   apps: [{
     name: 'my-app',
     script: './index.js',
     interpreter: '/home/takeuchi/.nvm/versions/node/v20.0.0/bin/node',
   }]
 }

したがって pm2 デーモン自体が v24 で動いていても、アプリは v20 で動く、といった構成が可能。
`interpreter` を省略すると PATH 上の node が使われるため、''本番では明示的に指定する'' のが安全。

*** `~/bin/pm2` ラッパーで nvm を意識しない運用 [#f20a13be]

プロジェクトフォルダに `.nvmrc` があって別バージョンの node が有効になっていると、そのフォルダーからユーザーグローバルの `pm2` コマンドが見えなくなる。

これを解決するために、''常に default の node 環境の pm2 を呼ぶラッパースクリプト'' を `~/bin/pm2` として用意しておく。

 LANG:bash
 #!/bin/bash
 export NVM_DIR="$HOME/.nvm"
 [ -s "$NVM_DIR/nvm.sh" ] && source "$NVM_DIR/nvm.sh"
 nvm use default --silent # pm2 を呼び出すために PATH を設定
 exec "$NVM_DIR/versions/node/$(nvm version default)/bin/pm2" "$@"

 LANG:console
 $ chmod +x ~/bin/pm2

このスクリプトを作ったうえで `~/bin` を PATH の先頭に置いておけば、どのプロジェクトフォルダにいても `pm2` コマンドは常に default 環境のものが呼ばれる。`$@` で全引数を透過的に渡すので、通常の pm2 コマンドがそのまま使える。
このスクリプトを作ったうえで `~/bin` を PATH に入れておけば((再起動後に)デフォルトで入る場合が多い)、どのプロジェクトフォルダにいても `pm2` コマンドは常に default 環境のものが呼ばれる(それ以外に pm2 がインストールされていない限り)。上記スクリプトは `$@` で全引数を透過的に渡しているので、通常の pm2 サブコマンドがそのまま使える。

 LANG: console
 $ pm2 list
 $ pm2 restart my-app
 $ pm2 save

pm2 自体は常に default の node で動き、''アプリが使う node は `interpreter` で個別指定'' という役割分担が明確になる。
こうすることで pm2 自体は常に default の node で動き、''アプリが使う node は `interpreter` で個別指定'' という役割分担が明確になる。

プロジェクトの `.nvmrc` は pm2 には無関係になり、nvm との関係をほぼ意識しなくてよくなる。

** 起動時の自動起動:crontab @reboot + ラッパースクリプト [#p176f0a3]

ユーザーレベルの crontab を使うことにより、
pm2 で起動したプロセスを、マシンのリブート後に自動で再起動することが可能になる。

このためにはまず、必要なプロセスをすべて立ち上げた状態で、

 LANG:console
 $ pm2 save

を行い、プロセスリストを保存しておく。

また、起動ログを取るために

 LANG:console
 $ mkdir -p ~/logs

としたうえで、

 $ crontab -e
 @reboot /home/takeuchi/bin/pm2 resurrect >> /home/takeuchi/logs/reboot-pm2.log 2>&1

のように設定すれば、pm2 save したプロセスがマシン起動時に自動実行され、
その際のログが ~/logs/reboot-pm2.log に記録される。

*** ポイント [#t1436625]

- cron では .bashrc が読まれず nvm の PATH が設定されないが、~/bin/pm2 スクリプトが正しく PATH を設定したうえで pm2 を呼んでくれる
- Node バージョンを変えても `nvm alias default vXX.XX.X` するだけでスクリプトは変更不要
- `pm2 resurrect` で `~/.pm2/dump.pm2` に保存済みのプロセスをすべて復元する
- アプリを追加・削除したら `pm2 save` し直すだけでよい
- ログに残しておくと起動失敗時の調査に役立つ

*** 起動タイミングについて
*** 起動タイミングについて [#g718c159]

`@reboot` は OS 起動直後に実行されるため、ネットワークや DB がまだ立ち上がっていない場合がある。
ただし、アプリ側で接続リトライを実装しているか、pm2 の再起動設定を入れておけば実用上は問題になりにくい。

 LANG:js
 // pm2.config.cjs
 module.exports = {
   apps: [{
     name: 'my-app',
     script: './index.js',
     max_restarts: 10,
     restart_delay: 5000,  // 再起動まで 5 秒待つ(ms)
   }]
 }

「DB に繋がらないから即落ちる」作りのアプリでなければ、これで吸収できるはず。

** systemd への登録について [#e1d365ec]

リブート後の pm2 再起動には、
sudo が使えるユーザー限定の代替手段として、
`pm2 startup` を使って systemd サービスファイルを生成しておく方法もある。

これはこれで `journalctl` でログを確認できる、起動順を `network.target` の後にできるといったメリットはあるのだが・・・

''systemd に焼き込まれるのは実行時のフルパス'' なので:

 ExecStart=/home/takeuchi/.nvm/versions/node/v24.14.1/bin/pm2 resurrect

後から `nvm alias default` を変えても systemd は古いパスを呼び続ける。

日常の pm2 操作はログインシェルで直接行うことがほとんどなので、systemd で pm2 プロセス自体を管理できるメリットもあまり実感しにくい。

nvm を使っている環境では systemd ではなく、''crontab + ラッパースクリプトの方が追従性・シンプルさの面で素直'' と思われる。


** まとめ [#b988b129]

 ユーザー takeuchi
   └── nvm default (v24) の node + pm2(グローバル)
         ├── ~/bin/pm2 ラッパー経由で常に default の pm2 を呼ぶ
         └── crontab @reboot + ラッパースクリプトで自動起動
               └── ~/.pm2/ がそのユーザーの管理空間
                     └── 各アプリは interpreter で Node バージョンを個別指定

- pm2 はグローバルに1つ、プロジェクトには入れない
- pm2 用 Node バージョンは `nvm alias default` で固定
- アプリが使う Node バージョンは `pm2.config.cjs` の `interpreter` で明示
- `~/bin/pm2` ラッパーにより、どのフォルダからでも nvm を意識せず pm2 を呼べる
- 自動起動は crontab + ラッパースクリプトが nvm 環境では素直
- 起動タイミング問題は pm2 の `max_restarts` + `restart_delay` で吸収する

* コメント・質問 [#hc3d06c7]

#article_kcaptcha

Counter: 33 (from 2010/06/03), today: 4, yesterday: 6