# 用 PM2 运行 Strapi

> Source: https://strapi.nodejs.cn/cms/deployment/guides/pm2

🌐 Running Strapi with PM2

构建管理面板，在一个 `ecosystem.config.js` 文件中描述你的应用，并将 `NODE_ENV` 设置为 `production`，然后用 `pm2 start` 启动它。运行 `pm2 startup` 和 `pm2 save`，这样应用在重启后会自动恢复。

从 `npm run start` 开始，Strapi 会在前台运行，当你关闭终端或者进程崩溃时就会停止。使用进程管理器可以让它持续运行：它会在应用失败时重启应用，重启后恢复运行，并收集日志。PM2 是 Node.js 应用中常见的选择。本指南介绍了如何安装 PM2，如何向它描述一个 Strapi 应用，以及之后的一些操作任务。

🌐 Started with `npm run start`, Strapi runs in the foreground and stops when you close the terminal or when the process crashes. A process manager keeps it running instead: it restarts the application on failure, brings it back after a reboot, and collects its logs. PM2 is a common choice for Node.js applications. This guide covers installing PM2, describing a Strapi application to it, and the operational tasks that follow.

:::prerequisites

- 一个部署到你服务器上的 Strapi 5 应用，并且已安装依赖（参见[部署指南](/cms/deployment#general-guidelines)）。
- [Node.js](https://nodejs.cn/) 以及服务器上可用的软件包管理器。
- Shell 访问，在重启步骤中具有 `sudo` 权限。

:::

## 安装 PM2 {#install-pm2}

🌐 Install PM2

全局安装 PM2，这样 `pm2` 命令就可以在你的终端和启动脚本中使用了：

🌐 Install PM2 globally so the `pm2` command is available to your shell and to the startup script:

```bash
yarn global add pm2
```
```bash
npm install -g pm2
```

确认安装：

🌐 Confirm the installation:

```bash
pm2 --version
```

## 搭建管理面板 {#build-the-admin-panel}

🌐 Build the admin panel

管理员面板是一个静态包，必须在生产环境中服务器启动前构建。使用 `NODE_ENV` 设置为 `production` 来构建，这样构建就会使用你的生产配置：

🌐 The admin panel is a static bundle that must be built before the server starts in production. Build it with `NODE_ENV` set to `production`, so the build uses your production configuration:

```bash
NODE_ENV=production yarn build
```
```bash
NODE_ENV=production npm run build
```

:::caution

把 `NODE_ENV=production` 设置在构建命令本身上，而不是整个 shell 会话。提前导出它再安装依赖会让包管理器跳过构建需要的 `devDependencies`。

🌐 Set `NODE_ENV=production` on the build command itself rather than for the whole shell session. Exporting it before installing dependencies makes the package manager skip `devDependencies`, which the build needs.

:::

## 创建一个生态系统文件 {#create-an-ecosystem-file}

🌐 Create an ecosystem file

PM2 可以从命令行启动应用，但配置文件可以将设置纳入版本控制，并使每次部署都可重复。在你的 Strapi 项目根目录下创建该文件：

🌐 PM2 can start an application from the command line, but a configuration file keeps the settings under version control and makes each deployment repeatable. Create the file at the root of your Strapi project:

```js title="/ecosystem.config.js"
module.exports = {
  apps: [
    {
      name: 'strapi',
      cwd: '/srv/strapi',
      script: 'npm',
      args: 'start',
      env: {
        NODE_ENV: 'production',
        HOST: '0.0.0.0',
        PORT: 1337,
      },
    },
  ],
};
```

这些选项有以下用途：

🌐 The options serve the following purposes:

| 选项 | 作用 |
|--------|---------|
| `name` | 你传给其他 PM2 命令的名字，比如 `pm2 restart strapi`。 |
| `cwd` | 项目的绝对路径。PM2 会相对于它解析 `script`，这样无论你从哪个目录运行 `pm2`，应用都能正确启动。 |
| `script` 和 `args` | PM2 执行的命令。使用包管理器而不是文件路径可以保持行为与手动启动 Strapi 一样。 |
| `env` | 进程的环境变量。`NODE_ENV` 必须是 `production`，这样 Strapi 才能加载生产配置。 |

:::warning Secrets in version control

如果生态系统文件会被提交到版本控制，不要把像 `APP_KEYS`、`ADMIN_JWT_SECRET` 或数据库凭证这样的秘密放进去。把它们保存在服务器的 `.env` 文件里，Strapi 会在启动时读取，或者从你的部署工具中注入（参见 [环境配置](/cms/configurations/environment)）。

🌐 Do not put secrets such as `APP_KEYS`, `ADMIN_JWT_SECRET`, or database credentials in the ecosystem file if it is committed to version control. Keep them in the server's `.env` file, which Strapi reads on startup, or inject them from your deployment tooling (see [environment configuration](/cms/configurations/environment)).

:::

## 用 PM2 启动 Strapi {#start-strapi-with-pm2}

🌐 Start Strapi with PM2

从生态系统文件启动应用：

🌐 Start the application from the ecosystem file:

```bash
pm2 start ecosystem.config.js
```

检查一下它是否显示出来了：

🌐 Check that it came up:

```bash
pm2 list
```

Strapi 条目应该显示 `online` 状态。显示 `errored` 状态，或者重启次数持续增加，意味着进程正在失败并退出。查看日志以了解原因：

🌐 The Strapi entry should show a status of `online`. A status of `errored`, or a restart count that keeps climbing, means the process is failing and exiting. Read the logs to find out why:

```bash
pm2 logs strapi --lines 100
```

## 重启后保持 Strapi 运行 {#keep-strapi-running-after-a-reboot}

🌐 Keep Strapi running after a reboot

PM2 本身无法在重启后继续运行。它需要一个初始化脚本，以及一个保存了要启动的应用的快照。

🌐 PM2 does not survive a reboot on its own. It needs an init script, and a saved snapshot of which applications to start.

1. 生成初始化脚本。该命令会打印第二个命令，为你的初始化系统和用户预先填好：

   ```bash
   pm2 startup
   ```

2. 按照它打印出来的命令运行，完全照着打印的执行。就是那个需要 `sudo` 的命令。
3. 保存当前的进程列表，这样 PM2 就知道要恢复什么：

   ```bash
   pm2 save
   ```

每当你添加、删除或重命名应用时，请再次运行 `pm2 save`。如果不这样做，PM2 会将列表恢复到上次保存时的状态。

🌐 Run `pm2 save` again whenever you add, remove, or rename an application. Without it, PM2 restores the list as it was at the last save.

## 管理日志 {#manage-logs}

🌐 Manage logs

PM2 会把每个应用的输出写入 `~/.pm2/logs/` 文件中。这些文件会无限增长，所以安装日志轮转模块：

🌐 PM2 writes each application's output to files in `~/.pm2/logs/`. These files grow without limit, so install the log rotation module:

```bash
pm2 install pm2-logrotate
```

设置大小限制以及要保留的轮换文件数量：

🌐 Set a size limit and how many rotated files to keep:

```bash
pm2 set pm2-logrotate:max_size 10M
pm2 set pm2-logrotate:retain 7
```

## 更新正在运行的部署 {#update-a-running-deployment}

🌐 Update a running deployment

每当你的代码或管理员配置发生变化时，都必须重新构建管理面板打包包，并且重建必须在进程重启之前完成。按照这个顺序部署：

🌐 The admin panel bundle must be rebuilt whenever your code or admin configuration changes, and the rebuild has to happen before the process restarts. Deploy in this order:

1. 去服务器上获取新代码。
2. 安装依赖。
3. 用 `NODE_ENV=production` 搭建管理面板。
4. 重启应用：

   ```bash
   pm2 restart strapi --update-env
   ```

`--update-env` 标志会让 PM2 重新读取环境，而不是重复使用进程第一次启动时的值。如果没有它，对你的 `.env` 文件或生态系统文件的更改将会被忽略。

🌐 The `--update-env` flag makes PM2 re-read the environment rather than reusing the values from when the process first started. Without it, changes to your `.env` file or ecosystem file are ignored.

:::note

PM2 仅在集群模式下执行零停机的 `pm2 reload`，此时它会在淘汰旧工作进程之前先启动替换的工作进程。在上面使用的单进程 fork 模式下，进程会停止然后重新启动，因此会有一个短暂的时间窗口内请求会失败。如果你想在这个时间窗口内控制访客看到的内容，可以在 Strapi 前面放一个反向代理，具体操作可以参考 [Nginx](/cms/deployment/guides/nginx)、[Caddy](/cms/deployment/guides/caddy)、[HAProxy](/cms/deployment/guides/haproxy) 和 [Traefik](/cms/deployment/guides/traefik) 指南。

🌐 PM2 only performs a zero-downtime `pm2 reload` in cluster mode, where it starts replacement workers before retiring the old ones. In the single-process fork mode used above, the process stops and starts again, so there is a short window where requests fail. Put a reverse proxy in front of Strapi if you need to control what visitors see during that window, as covered in the [Nginx](/cms/deployment/guides/nginx), [Caddy](/cms/deployment/guides/caddy), [HAProxy](/cms/deployment/guides/haproxy), and [Traefik](/cms/deployment/guides/traefik) guides.

:::

## 运行多个实例 {#run-more-than-one-instance}

🌐 Run more than one instance

PM2 可以通过其集群模式运行应用的多个副本，使用同一个端口。集群模式基于 Node.js 的 `cluster` 模块，需要一个 JavaScript 入口点。本指南前面使用的 `script: 'npm'` 值运行的是二进制文件，而 PM2 只能在 fork 模式下管理二进制文件。

🌐 PM2 can run several copies of an application with its cluster mode, sharing one port. Cluster mode builds on the Node.js `cluster` module, which needs a JavaScript entry point. The `script: 'npm'` value used earlier in this guide runs a binary, and PM2 can only manage a binary in fork mode.

在你项目的根目录添加一个入口点，直接启动 Strapi：

🌐 Add an entry point at the root of your project that starts Strapi directly:

```js title="/server.js"
const strapi = require('@strapi/strapi');

strapi.createStrapi(/* {...} */).start();
```

:::note

对于 TypeScript 项目，将 `distDir` 选项传给 `createStrapi`，这样服务器就会从编译后的输出启动（参见 [使用 createStrapi 工厂](/cms/typescript/development#use-the-createstrapi-factory)）。

🌐 For a TypeScript project, pass the `distDir` option to `createStrapi` so the server starts from the compiled output (see [using the createStrapi factory](/cms/typescript/development#use-the-createstrapi-factory)).

:::

把生态系统文件指向那个文件并选择集群模式：

🌐 Point the ecosystem file at that file and select cluster mode:

```js title="/ecosystem.config.js"
module.exports = {
  apps: [
    {
      name: 'strapi',
      cwd: '/srv/strapi',
      // highlight-start
      script: './server.js',
      exec_mode: 'cluster',
      instances: 'max',
      // highlight-end
      env: {
        NODE_ENV: 'production',
        HOST: '0.0.0.0',
        PORT: 1337,
      },
    },
  ],
};
```

Strapi 在这种模式下运行，但各实例之间没有协调，所以一些行为会发生变化。在启用之前先阅读这一部分。

🌐 Strapi runs in this mode, but it has no coordination between instances, so several behaviors change. Read this section before enabling it.

:::warning Concurrent schema synchronization

Strapi 会在启动时运行它的模式同步，每个进程执行一次，并且实例之间没有共享锁。如果重启时内容类型没有改变是安全的，因为同步会检测到模式没变并且不会做任何操作。

🌐 Strapi runs its schema synchronization on startup, once per process, with no lock shared between instances. A restart that leaves the content-types unchanged is safe, because the synchronization detects an unchanged schema and does nothing.

在两种情况下同步是不安全的：一种是会更改内容类型的发布，另一种是有待处理迁移的发布。实例一起启动后，会对同一个数据库发起并发的架构更改和迁移。

🌐 The synchronization is not safe in 2 cases: a release that changes content-types, and a release with pending migrations. Instances starting together then issue concurrent schema changes and migrations against the same database.

对于这些版本，先启动或运行一个实例，让它完成启动后再启动下一个。这是对同一个数据库运行多个进程的特性，所以把实例分散到不同主机上也无法避免。

🌐 For those releases, start or roll a single instance first and let it finish booting before the next one starts. This is a property of running several processes against one database, so spreading the instances across separate hosts does not avoid it.

:::

运行多个实例还有另外三个值得了解的后果：

🌐 Running several instances has 3 other consequences worth knowing:

- [CRON 任务](/cms/configurations/cron) 是在每个 Strapi 进程中调度的，所以一个任务是每个实例运行一次，而不是总共运行一次。设置为每晚运行的任务将根据你的实例数量运行多次。如果你的项目将 `cron.enabled` 设置为 `true`，要么把任务移出 Strapi，要么只在一个实例上保留它们。
- Strapi 里的任何内存数据都只属于一个实例，因为每个实例都是一个独立的进程。Strapi 核心中没有东西会在它们之间同步状态。
- 默认的本地上传提供商会将文件写入实例自己的磁盘。共享同一台机器和项目目录的实例会共享该磁盘，但不同机器或不同容器中的实例不会。在这种情况下，请使用由对象存储支持的[媒体库提供商](/cms/configurations/media-library-providers)之一。

在这些限制下，先从一个放在反向代理后的实例开始。只有在你测量发现一个不够用时，再添加实例，并按照上面描述的顺序进行部署。

🌐 Given these constraints, start with a single instance behind a reverse proxy. Add instances once you have measured that one is not enough, and sequence those deployments as described above.

## 确认应用正在运行 {#verify-the-application-is-running}

🌐 Verify the application is running

确认应用正在运行并且可以访问：

🌐 Confirm the application is running and reachable:

1. 运行 `pm2 list` 并检查 Strapi 条目是否显示 `online`，重启次数没有增加。
2. 请求健康检查路径，当服务器准备就绪时会返回 HTTP `204 No Content`：

   ```bash
   curl -I http://localhost:1337/_health
   ```

3. 重启服务器。
4. 再次运行 `pm2 list`。应用应该是 `online`，即使你没有启动它。

##  故障排除 {#icon-namebug--troubleshooting}

以下每个症状都指向一个特定的原因。症状以粗体显示，后面是引起它的原因以及需要改变的内容：

🌐 Each of the following symptoms points at a specific cause. The symptom is in bold, followed by what causes it and what to change:

- **该过程在 `pm2 list` 中显示 `errored`。** 阅读 `pm2 logs strapi --lines 100`。 缺少的构建、无法访问的数据库或缺失的环境变量通常是原因。
- **PM2 循环重启 Strapi。** 应用启动后立即退出。日志里写了原因。在修复之前，用 `pm2 stop strapi` 停止循环，而不是让它不断重试。
- **Strapi 在重启后没有恢复。** `pm2 startup` 打印的命令要么从未运行过，要么在添加应用后没有运行 `pm2 save`。请重复这两个步骤。
- **对环境变量的更改没有效果。** PM2 会重用进程最初启动时的环境。使用 `pm2 restart strapi --update-env` 重启。
- **部署后管理员面板显示的是旧版本。** 管理员包没有重新构建。使用 `NODE_ENV=production` 运行构建，然后重启。

## 下一步 {#next-steps}

🌐 Next steps

<NextSteps title="">
  <NextSteps.Step
    title="Put a reverse proxy in front of Strapi"
    description="Terminate HTTPS with Nginx, Caddy, HAProxy, or Traefik."
    link="/cms/deployment/guides/nginx"
  />
  <NextSteps.Step
    title="Review the server configuration options"
    description="The full list of options available in the server config file."
    link="/cms/configurations/server"
  />
