07 第一次完整上线
本章把前面的知识串起来。示例以常见的 Ubuntu/Debian + systemd 为背景;不同发行版的 Nginx 配置目录和包管理命令可能不同。
1. 上线前准备清单
需要知道:
- 服务器地址和 SSH 用户;
- 域名是否解析到服务器;
- 项目需要的 Node.js 版本;
- MySQL/Redis 地址;
- 生产环境变量;
- Express 端口;
- 前端静态目录;
- 谁负责数据库 migration 和备份。
不要到了生产服务器才临时决定数据库密码、端口和目录。
2. 使用普通部署用户
假设使用 deploy 用户。应用目录应归它所有,而不是让 Node.js 长期以 root 运行。
目录示例:
/var/www/nloop/current
如果需要管理员创建目录,可以使用类似:
sudo install -d -o deploy -g deploy /var/www/nloop/current
之后以 deploy 用户完成源码、依赖、构建和 PM2 操作。
3. 安装并固定 Node.js
按照 NVM 官方安装方法安装 NVM 后:
nvm install 22
nvm alias default 22
nvm use 22
node --version
npm --version
这里的 22 是教程示例。应使用项目经过验证且仍受支持的 Node.js 版本。
安装 PM2:
npm install --global pm2@latest
pm2 --version
which pm2
4. 获取项目代码
可以通过 Git、CI 构建产物或内部发布包。使用 Git 时示意:
cd /var/www/nloop
git clone <你的仓库地址> current
cd current
私有仓库的 SSH key 和 access token 属于敏感凭证,应使用最小权限,不能写进教程、仓库或日志。
5. 准备生产环境变量
创建:
/var/www/nloop/current/.env
示例内容:
NODE_ENV=production
HOST=127.0.0.1
PORT=8080
DB_HOST=127.0.0.1
DB_PORT=3306
DB_NAME=nloop
DB_USER=nloop_app
DB_PASSWORD=replace-with-real-secret
限制权限:
chmod 600 .env
不要在终端执行 cat .env 后把输出复制到工单或聊天中。
6. 安装依赖并构建
如果服务器负责构建:
npm ci
npm run typecheck
npm run build
npm prune --omit=dev
检查构建入口:
ls -l dist/server.js
如果不存在,不要继续启动 PM2。先检查 tsconfig.build.json 的 rootDir、outDir、include 和实际输出结构。
7. 先直接运行应用
NODE_ENV=production node dist/server.js
从另一个 SSH 会话测试:
curl -i http://127.0.0.1:8080/health
期望类似:
HTTP/1.1 200 OK
Content-Type: application/json; charset=utf-8
{"status":"ok"}
验证完成后,用 Ctrl+C 正常停止这个前台进程,再交给 PM2。不要同时保留前台进程,否则 PM2 会因为 8080 端口占用而启动失败。
8. 准备 ecosystem 文件
在项目根目录创建 ecosystem.config.js。当前 package.json 没有 "type": "module",所以这个 .js 文件会按 CommonJS 解释:
module.exports = {
apps: [
{
name: "nloop-api",
cwd: "/var/www/nloop/current",
script: "./dist/server.js",
instances: 1,
exec_mode: "fork",
autorestart: true,
restart_delay: 1000,
time: true,
env: {
NODE_ENV: "production",
HOST: "127.0.0.1",
PORT: "8080",
},
},
],
};
环境变量不要同时在 .env、ecosystem、systemd 和 shell 中到处重复定义。第一版可以:
普通运行配置 → ecosystem
数据库密码等秘密 → .env
并在团队内形成固定约定。
9. 使用 PM2 启动
pm2 start ecosystem.config.js
pm2 list
pm2 show nloop-api
pm2 logs nloop-api --lines 100 --nostream
再次验证:
curl -i http://127.0.0.1:8080/health
如果 PM2 不断增加 restart 次数,说明应用处于崩溃循环,应先看 error log,而不是不断执行 restart。
10. 配置 Nginx
Ubuntu/Debian 常见配置文件:
/etc/nginx/sites-available/nloop
最小内容:
server {
listen 80;
server_name example.com;
root /var/www/frontend;
index index.html;
access_log /var/log/nginx/nloop-access.log;
error_log /var/log/nginx/nloop-error.log warn;
location / {
try_files $uri $uri/ /index.html;
}
location /api/ {
proxy_pass http://127.0.0.1:8080;
proxy_http_version 1.1;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
}
}
在采用 sites-enabled 的发行版中启用站点:
sudo ln -s /etc/nginx/sites-available/nloop \
/etc/nginx/sites-enabled/nloop
如果目标已经存在,不要使用强制覆盖命令,应先检查现有配置由谁维护。CentOS/RHEL 等系统可能使用 /etc/nginx/conf.d/,以实际发行版为准。
验证并加载:
sudo nginx -t
sudo systemctl reload nginx
sudo systemctl status nginx
11. 分层验证请求
# Express
curl -i http://127.0.0.1:8080/health
# 本机 Nginx,并指定虚拟主机
curl -i -H "Host: example.com" http://127.0.0.1/api/health
# 公网域名
curl -i http://example.com/api/health
每一步成功后再进入下一步,错误范围会小很多。
12. 配置 PM2 开机启动
以 deploy 用户执行:
pm2 startup
复制执行 PM2 输出的、包含实际 NVM PATH 的 sudo 命令。然后:
pm2 save
确认服务:
sudo systemctl status pm2-deploy
sudo systemctl is-enabled pm2-deploy
13. 验证重启恢复
服务器重启属于有影响操作,只能在有维护权限和维护窗口时进行。重启后检查:
node --version
sudo systemctl status nginx
sudo systemctl status pm2-deploy
pm2 list
curl -i http://127.0.0.1:8080/health
curl -i http://example.com/api/health
如果 systemd 显示 PM2 成功,而 pm2 list 为空,通常是没有正确 pm2 save、用户不一致或 PM2_HOME 不一致。
14. 首次上线验收表
- [ ] 项目使用固定 Node.js 版本;
- [ ]
npm ci成功; - [ ] 类型检查成功;
- [ ]
dist/server.js存在; - [ ] 直接运行 JavaScript 成功;
- [ ] PM2 应用状态稳定,不发生崩溃循环;
- [ ] Express 只监听预期地址和端口;
- [ ] Nginx 配置通过语法检查;
- [ ] 内网和公网健康检查成功;
- [ ] PM2 和 Nginx 日志可读取;
- [ ]
.env未提交 Git且权限受限; - [ ] PM2 startup 和 save 已完成;
- [ ] 已验证机器重启后的恢复能力;
- [ ] 已制定 HTTPS 上线计划。