开机自启与远程部署 / CI
定位: 把 PM2 从"我这台机器上跑着"升级为"服务器重启了它自己回来,发版不用人肉 ssh"。
分两部分:Part I systemd 开机自启;Part IIpm2 deploy远程发布与 CI 集成。
Part I · 开机自启(systemd)
问题与原理
默认情况下 PM2 daemon 是你手动跑起来的:机器一重启,daemon 没了,进程没人看护。所以要做两件事:
- 让系统开机时自动把 daemon 拉起来;
- daemon 起来后按你保存过的快照(dump.pm2)把应用进程恢复。
Linux 上的标准做法 = systemd 单元 + pm2 startup:
开机 → systemd 启动 pm2-<user>.service → 拉起 PM2 daemon → pm2 resurrect(按 dump.pm2 恢复进程)标准三步
以运行应用的普通用户(如 deploy)执行:
# ① 先把自己想要的进程都 start 好
pm2 start ecosystem.config.js --env production
# ② 冻结进程清单(dump.pm2)
pm2 save
# ③ 生成并注册 systemd 单元
pm2 startup
# 它会打印一条形如下面的命令(含当前用户的 PATH 与 home):
# sudo env PATH=$PATH:/usr/bin pm2 startup systemd -u deploy --hp /home/deploy
# 把这行"打印出来的"命令复制执行(需要 root)验证:
systemctl status pm2-deploy # 服务名 = pm2-<user>
systemctl cat pm2-deploy # 查看生成的 unit 内容
journalctl -u pm2-deploy # 看 daemon 启动日志
pm2 ls # 进程应当 online最后重启机器实测一次:sudo reboot 后回来 pm2 ls 应该看到进程自动回来了。
关键点与坑
| 点 | 说明 |
|---|---|
必须 pm2 save | startup 只负责"拉起 daemon + 恢复快照";快照不存在 = 恢复空气 |
| 每次增删/改参后重新 save | 快照是"当时的进程清单",不是实时同步的 |
| 用哪个用户跑 startup | 就用哪个用户跑应用;root 的应用 = root 的 daemon,普通用户看不见(反之亦然) |
| 换用户 | pm2 startup systemd -u www --hp /home/www(配合对应用户的 save) |
| 服务名 | pm2-<user>;可用 --service-name 自定义 |
| Node 路径变化后 | 重新生成:先 pm2 unstartup 执行其打印命令删除旧单元,再按新 PATH 重新 pm2 startup |
| 开机要等网络 | 官方建议在 unit 里加 Wants=network-online.target / After=network-online.target 依赖 |
| 不需要自启了 | pm2 unstartup → 复制执行打印的 sudo 命令,即移除 |
升级 Node 大版本后务必重做 startup:unit 里固化的是旧 PATH(如 nvm 版本路径),新版本 node 装好但自启仍指向旧路径,是最常见的"重启后服务起不来"原因之一。
容器里怎么办
容器内一般不需要 systemd:让 PM2 当前台进程(PID 1)跑,交给容器的 restart policy / 编排系统兜底:
# 方式一:pm2-runtime(随 PM2 提供,专为容器设计,作为 node 的 drop-in)
pm2-runtime start app.js -i max
# 方式二:PM2 前台运行 + --no-daemon
pm2 start app.js --no-daemon需要"一套独立、不互相干扰的 PM2"时可用
PM2_HOME=/path pm2 ...隔离(见 核心概念)。
Part II · 远程部署:pm2 deploy
它解决什么
传统发版:ssh 上服务器 → git pull → 重启。pm2 deploy 把"拉代码 → 装依赖 → 重启服务"固化成一条命令,支持多机并行、指定版本、回滚。
前置条件
- 远端服务器:已装 Node 与 PM2(官方明确要求远端先装好);
- 远端有权限 clone/pull 你的仓库(通常配置 ssh key);
- 本地到远端 ssh 免密;
- 仓库里带着
ecosystem.config.js(post-deploy 在远端执行时需要它,且通常把它纳入版本库)。
配置:deploy 块
deploy 配置挂在 ecosystem 文件顶层(apps 之外):
// ecosystem.config.js
module.exports = {
apps: [
{ name: 'api', script: './dist/server.js', exec_mode: 'cluster', instances: 'max' },
],
deploy: {
production: {
user: 'deploy', // 远端用户名
host: ['10.0.0.11', '10.0.0.12'], // 单机写字符串,多机写数组(并行部署)
ref: 'origin/main', // 部署哪个分支/引用
repo: 'git@github.com:org/api.git', // 仓库地址
path: '/var/www/api', // 远端部署根目录
'pre-deploy-local': '', // 本地先执行(如构建/打包)
'post-deploy': 'npm ci && pm2 startOrReload ecosystem.config.js --env production',
ssh_options: 'StrictHostKeyChecking=no',
},
// staging: { ... } // 可定义多套环境
},
};命令序列
| 命令 | 作用 |
|---|---|
pm2 deploy production setup | 首次部署:在远端创建目录结构并 clone 仓库(只需跑一次) |
pm2 deploy production | 部署到配置里 ref 指向的版本(拉取→post-deploy) |
pm2 deploy production <commit/tag> | 部署到指定版本 |
pm2 deploy production revert 1 | 回滚到上一个部署(数字=回退几步) |
pm2 deploy production curr / prev | 查看当前 / 上一次部署的提交 |
pm2 deploy production list | 列出历史部署 |
pm2 deploy production exec "pm2 reload all" | 在所有目标服务器上执行一次性命令 |
pm2 deploy production --force | 忽略本地未提交改动,强制部署 |
pm2 deploy production update | 更新 pm2-deploy 运行时相关(进阶用) |
pm2 deploy production setup # 第一次
pm2 deploy production # 以后每次发版就这一条
pm2 deploy production revert 1 # 出问题回滚配置文件若命名为
ecosystem.config.js或pm2.config.js,命令里可省略文件名;否则写作pm2 deploy ecosystem.config.js production。
post-deploy 最佳实践
post-deploy 在远端刚拉下来的代码里执行,典型的组合:
npm ci # 干净安装依赖(比 npm install 快且可复现)
npm run build # 需要构建就构建(产物进仓库则省略)
pm2 startOrReload ecosystem.config.js --env production # 幂等:在跑就 reload,没跑就 start- 用
startOrReload/startOrRestart而不是裸pm2 start:多跑几次不会把进程加重复。 - cluster 应用用 reload = 零停机发布(集群模式与零停机发布)。
- 若某台机器首次部署,post-deploy 的 reload 会退化为 start(幂等设计)。
常见进阶选项
| 项 | 说明 |
|---|---|
key | ssh 私钥路径,默认 $HOME/.ssh |
ssh_options | 额外 ssh 参数(字符串或数组) |
pre-setup / post-setup | setup 阶段前后在远端执行的脚本(一次性准备) |
pre-deploy / post-deploy | 每次部署前后执行的脚本 |
pre-deploy-local | 每次部署前在本地执行的脚本 |
| ssh 转发 | 需要在服务器上用你的 key 再拉私有依赖时,官方建议在 ~/.ssh/config 写 ForwardAgent yes 并 ssh-add |
接进 CI(GitHub Actions 示例)
思路:CI 里配置 ssh 私钥 → 直接调用 pm2 deploy(前提:CI 环境已装 pm2、能连目标机)。
# .github/workflows/deploy.yml
name: deploy
on:
push:
branches: [main]
jobs:
deploy:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with: { node-version: 20 }
- run: npm ci
- run: npm install -g pm2
- name: 配置 ssh
run: |
mkdir -p ~/.ssh
echo "${{ secrets.DEPLOY_SSH_KEY }}" > ~/.ssh/id_ed25519
chmod 600 ~/.ssh/id_ed25519
ssh-keyscan -H ${{ vars.DEPLOY_HOST }} >> ~/.ssh/known_hosts
- run: pm2 deploy production --force
env:
# 若 deploy 块里 user/host 想用变量,可用 sed 或让 host 用占位后替换
NODE_ENV: production若不引入 pm2 deploy,最简替代是"scp/rsync 产物 →
ssh host 'cd /path && pm2 reload ecosystem.config.js --env production'"——效果类似,少一层抽象。 安全:使用专用 deploy 账号与独立 key;私钥只放 CI secrets,绝不提交仓库;远端禁止 root 部署。
三种发版姿势对比
| 方式 | 特点 | 适用 |
|---|---|---|
pm2 deploy | 一条命令、版本/回滚/多机 | 中大型、git 流程规范的项目 |
| CI + ssh(手动命令) | 灵活、和现有 CI 深度集成 | 已有完善 CI 的团队 |
| 容器镜像 + 编排 | 不依赖 pm2 远程管理 | 已容器化的架构 |
参考链接
- 官方开机自启(startup):https://pm2.keymetrics.io/docs/usage/startup/
- 官方部署系统:https://pm2.keymetrics.io/docs/usage/deployment/
- Docker 集成(pm2-runtime):https://pm2.keymetrics.io/docs/usage/docker-pm2-nodejs/
