摘要:本文以两个 Node.js 项目(基金数据导航站、Markdown 在线编辑器)为例,完整记录如何在同一台云服务器 上用宝塔面板「Node 项目」托管多个服务:Express 后端 + pm2 进程守护 + nginx 按域名反向代理 + 端口隔离。并记录 WorkBuddy(AI 编程助手) 在其中的角色------生成部署脚本、SSH 自动化上线、趟平宝塔面板注册暗坑并沉淀为可复用技能。
隐私说明:文中域名(
fund.example.com/md.example.com)、服务器 IP、路径、项目名等均为示意占位,非真实地址;密码等敏感信息已全部脱敏。
目录
- 环境与技术栈
- 项目结构(以 Markdown 编辑器为例)
- WorkBuddy 在部署中的角色
- 本地验证
- 宝塔 Node 项目托管
- 同机多项目:端口隔离与 nginx 反代
- 一键部署脚本(deploy_md.py 思路)
- 域名、HTTPS 与面板注册坑点
- 常见问题(FAQ)
- 总结
一、环境与技术栈
| 项 | 配置 |
|---|---|
| 服务器 | 云服务器一台(公网 IP) |
| 面板 | 宝塔 Linux 面板 |
| Node 版本 | v24.18.0(面板 Node 版本管理器安装) |
| 进程守护 | pm2(宝塔 Node 项目底层) |
| Web 入口 | nginx 反向代理(宝塔托管配置) |
| AI 助手 | WorkBuddy(负责写代码、写部署脚本、SSH 上线、排障) |
两个项目:
| 项目 | 域名 | pm2 / 项目名 | 端口 | 技术 |
|---|---|---|---|---|
| 基金数据导航站 | fund.example.com | fund-app |
3000 | Node 服务 |
| Markdown 在线编辑器 | md.example.com | md_editor(后改名为域名) |
3100 | Express + markdown-it + DOMPurify + highlight.js |
二、项目结构(以 Markdown 编辑器为例)
markdown-editor/
├── package.json # 依赖与 start 脚本
├── server.js # Express 服务 + /api/docs 接口
├── public/ # 前端静态资源(编辑器 UI)
├── data/ # 文档存储(docs.json,后端读写)
└── node_modules/ # 安装依赖(不上传服务器)
package.json 关键依赖:
{
"dependencies": {
"express": "^4.19.2",
"markdown-it": "^14.1.0",
"dompurify": "^3.1.6",
"@highlightjs/cdn-assets": "^11.10.0"
},
"scripts": { "start": "node server.js" }
}
server.js 端口兜底(关键):
const PORT = parseInt(process.env.PORT, 10) || 3100;
app.listen(PORT, () => console.log(`running on http://localhost:${PORT}`));
后端提供 /api/docs 增删改查,文档落盘 data/docs.json;/healthz 用于健康检查。
三、WorkBuddy 在部署中的角色
这次部署不是"人手工敲命令",而是 WorkBuddy 承担了大部分工程与运维动作,人负责提需求和验收。具体拆成四块:
1. 产出项目代码
Express 服务、编辑器前端、/api/docs 接口、渲染管线(markdown-it + DOMPurify + highlight.js)由 WorkBuddy 直接生成,按需求迭代。
2. 生成部署脚本 deploy_md.py
用 Python paramiko 走 SSH,自动完成:连服务器 → 探测环境 → 建目录 → SFTP 上传(排除 node_modules/data)→ 服务器 npm install --production → 写 ecosystem.config.js → pm2 启停 → 写 nginx 反代 → nginx -t/reload → 健康检查。
3. SSH 自动化上线
连服务器、装依赖、配 pm2 守护、写 nginx 反向代理、申请 HTTPS------这些易错重复步骤由 WorkBuddy 执行。
4. 排雷并沉淀为技能 baota-ssh-deploy
宝塔「Node 项目」注册的暗坑(见第八节)由 WorkBuddy 现场逐个解决,并进一步把"SSH 部署 + 面板原生 Node 项目注册"封装成可复用技能 baota-ssh-deploy,后续换服务器直接调用,无需重复趟坑。
可复用收益:部署脚本与技能沉淀后,新增站点 / 换服务器时,工作量从"重新踩坑"降为"调一次技能"。
四、本地验证
npm install
npm start
# 访问 http://localhost:3100 与 http://localhost:3100/healthz
确认本地能跑、接口正常再上线。
五、宝塔 Node 项目托管
- 宝塔面板 → 网站 → Node 项目 → 创建项目。
- 运行目录:
/www/wwwroot/md.example.com/ - 启动脚本:
node server.js - 端口:
3100(基金站用3000) - Node 版本:选
v24.18.0 - 勾选开机启动 、进程守护(底层即 pm2)。
部署目录建议结构:
/www/wwwroot/md.example.com/
├── server.js
├── public/
├── data/ # 运行期生成 docs.json
└── package.json
上传时排除
node_modules/与已有data/docs.json,依赖让服务器npm install现装。
六、同机多项目:端口隔离与 nginx 反代
核心思路 :一台服务器、多个 Node 服务,各自监听不同端口,由 nginx 按 server_name 反代到对应 127.0.0.1:端口。
绑定域名后,宝塔自动生成反代配置(示意):
server {
listen 80;
server_name md.example.com;
location / {
proxy_pass http://127.0.0.1:3100;
proxy_http_version 1.1;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "upgrade";
}
}
基金站同理,server_name fund.example.com; → proxy_pass http://127.0.0.1:3000;。端口、域名、运行用户(编辑器 root、基金站 www)彼此隔离。
七、一键部署脚本(deploy_md.py 思路)
可参考以下流程自写脚本,用 paramiko 实现:
- SSH 连接服务器(密码或密钥,不要硬编码明文,建议读环境变量);
- 探测:系统架构、Node/npm/pm2/nginx 路径、磁盘与内存;
mkdir -p /www/wwwroot/<domain>;- SFTP 上传项目(排除
node_modules、.git、data/docs.json); - 服务器执行
npm install --production; - 写
ecosystem.config.js(pm2),指定PORT、DATA_DIR等 env; pm2 delete <老名>→pm2 start ecosystem.config.js→pm2 save;- 写 nginx 反代配置 →
nginx -t→nginx -s reload; curl健康检查(本地端口 + 域名)。
八、域名、HTTPS 与面板注册坑点
HTTPS:Node 项目「SSL」申请 Let's Encrypt 免费证书,开启强制 HTTPS。
面板注册 Node 项目的真实坑(WorkBuddy 现场解决):
- 项目名正则
^\w+$:带点域名(如md.example.com)不能直接作项目名。做法:先以md_editor创建,再改库把sites.name与project_config.project_name改为域名,并重命名vhost/pids/{name}.pid、vhost/scripts/{name}.sh、/www/wwwlogs/nodejs/{name}.log,运行期启停不受影响。 public.dict_obj()无参 :本版宝塔dict_obj()不接受参数,须属性赋值:g = public.dict_obj(); g.project_name='...'; g.domains=[...]。- 端口占用 :创建前先从 pm2 删除旧同名/同端口进程,否则
check_port_is_used报冲突。 start_project不注入 PORT :server.js必须|| 3100兜底,否则与已占 3000 的基金站冲突。- 域名绑定两步 :
project_add_domain加域名 →bind_extranet开启外网映射,才生成面板托管的node_<domain>.conf。 - 手工 nginx 配置先删 :若之前手动建过
md.example.com.conf,绑定官方域名前要删掉,避免server_name重复冲突。
九、常见问题(FAQ)
Q1:项目起不来,日志显示端口被占用?
同机已有服务占了该端口。改端口(如 3100)并在 server.js 兜底,重启即可。
Q2:面板显示项目但域名访问 502?
反代 proxy_pass 指向的 127.0.0.1:端口 与实际监听不一致;确认服务在跑、/healthz 本地 200。
Q3:上传后页面空白?
public/ 路径或 express.static 目录不对;确认 server.js 中 express.static(path.join(__dirname,'public')) 且前端资源已上传。
Q4:npm install 失败?
服务器镜像源是否可达;可换 npmmirror,或用 --ignore-scripts 重试。
Q5:重启服务器后项目没自动起?
确认勾选了开机启动(pm2 startup + save)。
十、总结
多个 Node.js 服务同机部署的稳妥范式:
宝塔 Node 项目托管 + pm2 进程守护 + nginx 按域名反代 + 端口隔离
两个站(基金导航站 3000、Markdown 编辑器 3100)共用一台服务器,互不干扰、各自 HTTPS、可独立重启。把"写代码 + 部署脚本 + SSH 上线 + 排障"交给 WorkBuddy 这类 AI 编程助手后,人只需提需求和验收,部署从"硬扛"变成"验收"。
相关标签:#Node.js #宝塔面板 #pm2 #nginx #反向代理 #Express #WorkBuddy #AI部署 #部署实战 #HTTPS
版权声明:本文为博主原创,由 WorkBuddy 辅助撰写与部署,转载请附上原文出处链接。