部署架构总结

远程构建部署(deploy-remote.sh)和 Docker部署,项目中的地址只是实例换成自己的

一、基础设施

组件值
服务器AWS EC2 52.77.230.213
用户ec2-user
SSH 密钥c:\Users\chang.ssh\sg-xiangyu.pem
域名wescores.net(HTTPS)
项目源码C:\projects\stativo
项目路径(服务器)/var/www/stativo/
部署脚本scripts/deploy-remote.sh
PM2 配置ecosystem.config.js
环境变量.env.production(不在 Git 中)

二、运行架构

1
2
3
4
5
用户 → HTTPS(443) → Nginx → proxy_pass http://127.0.0.1:3000
                                      ↓
                              PM2 集群模式(4实例)
                                      ↓
                      Next.js Standalone Server

三、服务器目录结构

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
/var/www/stativo/
├── current/              → 符号链接,指向最新 release
├── previous/             → 符号链接,指向上一个 release(回滚用)
├── releases/             → 按时间戳命名的发布版本(保留最近3个)
│   ├── 20260918112041/   → 最新部署
│   │   ├── .next/standalone/  ← PM2 实际运行入口
│   │   ├── .env.production   ← 环境配置
│   │   ├── ecosystem.config.js ← PM2 配置
│   │   └── scripts/deploy-remote.sh ← 部署脚本
│   └── ...
└── shared/              ← SSL 验证等共享文件

四、关键配置文件

4.1 deploy-remote.sh(部署脚本)

位置:scripts/deploy-remote.sh

8 步流程:

步骤操作说明
1/8解压源码tar xzf 到新 release 目录,删除原始包
2/8复制环境变量优先 /tmp/.env.production > shared > current
3/8pnpm install–frozen-lockfile,锁定依赖版本
4/8pnpm buildsource .env.production 后构建
5/8验证 standalone检查 server.js 存在,复制 static + public
6/8切换 current旧 current 存为 previous,新 release 链为 current
7/8PM2 重启删除旧进程,从新 release 的 ecosystem.config.js 启动
8/8健康检查curl 127.0.0.1:3000,2xx 即成功,清理旧 release

失败时输出回滚命令:

1
ln -sfn $(readlink /var/www/stativo/previous) /var/www/stativo/current && pm2 reload stativo

4.2 ecosystem.config.js(PM2 配置)

位置:ecosystem.config.js

  • 4 实例集群模式(cluster)
  • 端口 3000
  • HOSTNAME 强制 0.0.0.0(避免系统 HOSTNAME 干扰健康检查)
  • 内存重启阈值 1G
  • 日志模式可配:off / error / all(默认 off,写 /dev/null)
  • 环境变量由部署脚本注入:PM2_APP_NAME、PM2_APP_PORT、PM2_APP_CWD

4.3 next.config.ts(构建配置)

  • typescript.ignoreBuildErrors: true(临时关闭,遗留类型错误较多,后续修复后改回 false)
  • eslint.ignoreDuringBuilds: true
  • outputFileTracingIncludes 包含 jose 模块(Next.js standalone 不自动追踪)
  • standalone 模式构建

4.4 Nginx 配置

位置:/etc/nginx/conf.d/stativo.conf

  • proxy_pass 指向 127.0.0.1:3000
  • SSL 证书:/etc/pki/tls/certs/wescores.net-origin.pem
  • HTTPS 443 → HTTP 3000 反向代理

4.5 .env.production(环境变量)

关键配置:

1
2
3
4
5
6
7
NEXT_PUBLIC_SITE_NAME=wescores
NEXT_PUBLIC_SITE_DOMAIN=wescores.net
NEXT_PUBLIC_SITE_URL=https://wescores.net
NEXT_PUBLIC_API_URL=https://api.6wn.net/api
AUTH_API_URL=https://api.6wn.net/api
AUTH_TRUST_HOST=true
NEXTAUTH_URL=https://wescores.net

五、更新部署流程

5.1 前置条件

必须先提交代码。 部署基于 git archive HEAD,只有已提交的代码才会参与部署。

1
2
3
4
cd C:\projects\stativo
git add .
git commit -m "type(scope): description"
git status  # 确认 "working tree clean"

5.2 方式一:说"更新部署发布"

先装个 AI 搭档 我推荐 [Trae] (https://www.trae.cn/?utm_source=advertising&utm_medium=xiaolincoding_ug_cpa&utm_term=hw_trae_xiaolincoding, “中文友好,界面简洁”)。 对trae-ai说"更新部署发布",trae自动执行全部步骤:

  1. git status 确认工作区干净
  2. git archive --format=tar.gz HEAD 打包最新提交
  3. scp 上传三个文件到服务器 /tmp:
    • stativo-deploy.tar.gz(代码包)
    • .env.production(环境变量)
    • scripts/deploy-remote.sh(部署脚本)
  4. SSH 执行 bash /tmp/deploy-remote.sh
  5. 等待 8 步完成,检查 DEPLOY_SUCCESS 或 HEALTH_FAIL
  6. 验证 https://wescores.net 返回 200
  7. 清理本地临时文件

5.3 方式二:手动部署

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
# 0. 确保代码已提交
cd C:\projects\stativo
git status  # 应显示 "working tree clean"

# 1. 打包代码(git archive HEAD,只含已提交代码)
git archive --format=tar.gz HEAD -o "$env:TEMP\stativo-deploy.tar.gz"

# 2. 上传三个文件
scp -i "c:\Users\chang\.ssh\sg-xiangyu.pem" -o ConnectTimeout=15 -o ServerAliveInterval=5 `
  "$env:TEMP\stativo-deploy.tar.gz" ec2-user@52.77.230.213:/tmp/

scp -i "c:\Users\chang\.ssh\sg-xiangyu.pem" -o ConnectTimeout=15 -o ServerAliveInterval=5 `
  "C:\projects\stativo\.env.production" ec2-user@52.77.230.213:/tmp/

scp -i "c:\Users\chang\.ssh\sg-xiangyu.pem" -o ConnectTimeout=15 -o ServerAliveInterval=5 `
  "C:\projects\stativo\scripts\deploy-remote.sh" ec2-user@52.77.230.213:/tmp/

# 3. 执行部署
ssh -i "c:\Users\chang\.ssh\sg-xiangyu.pem" -o ConnectTimeout=15 -o ServerAliveInterval=5 `
  ec2-user@52.77.230.213 "bash /tmp/deploy-remote.sh"

5.4 部署成功标志

1
2
3
4
5
6
7
8
9
[1/8] extracting... ✓
[2/8] copying env... ✓
[3/8] pnpm install... ✓
[4/8] pnpm build... ✓
[5/8] verifying standalone... ✓
[6/8] switching current... ✓
[7/8] PM2 restart... ✓ (4 instances online)
[8/8] health check... HEALTH_OK: HTTP 200
DEPLOY_SUCCESS: /var/www/stativo/releases/YYYYMMDDHHMMSS

5.5 部署失败处理

如果 HEALTH_FAIL,脚本会输出回滚命令,手动执行:

1
2
ssh -i "c:\Users\chang\.ssh\sg-xiangyu.pem" ec2-user@52.77.230.213 `
  "ln -sfn `$(readlink /var/www/stativo/previous) /var/www/stativo/current && pm2 reload stativo"

常见失败原因:

  • TypeScript 类型错误 → next.config.ts 当前已临时关闭
  • pnpm install 失败 → 检查网络或 lockfile
  • standalone server.js 缺失 → 构建失败
  • 健康检查超时 → 检查 PM2 日志或端口冲突

六、同步环境变量(单独更新 .env.production)

修改了 .env.production 但不想重新部署代码时:

1
2
3
4
5
6
# 上传到服务器并同步到 shared 目录
scp -i "c:\Users\chang\.ssh\sg-xiangyu.pem" `
  "C:\projects\stativo\.env.production" ec2-user@52.77.230.213:/tmp/.env.production

ssh -i "c:\Users\chang\.ssh\sg-xiangyu.pem" ec2-user@52.77.230.213 `
  "sudo cp /tmp/.env.production /var/www/stativo/shared/.env.production && sudo chown ec2-user:ec2-user /var/www/stativo/shared/.env.production && rm /tmp/.env.production"

注意:环境变量更新后需要重新部署或重启才能生效:

1
ssh -i "c:\Users\chang\.ssh\sg-xiangyu.pem" ec2-user@52.77.230.213 "pm2 reload stativo --update-env"

七、回滚到上一版本

1
2
ssh -i "c:\Users\chang\.ssh\sg-xiangyu.pem" ec2-user@52.77.230.213 `
  "ln -sfn `$(readlink /var/www/stativo/previous) /var/www/stativo/current && pm2 reload stativo"

八、常用运维命令

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
# SSH 连接服务器
ssh -i "c:\Users\chang\.ssh\sg-xiangyu.pem" ec2-user@52.77.230.213

# 查看 PM2 状态
pm2 list

# 查看实时日志
pm2 logs stativo --lines 50

# 重启应用(不重新部署)
pm2 reload stativo --update-env

# 检查 Nginx 配置
sudo nginx -t && sudo systemctl reload nginx

# 查看健康状态
curl -s -o /dev/null -w '%{http_code}' http://127.0.0.1:3000
curl -s -o /dev/null -w '%{http_code}' https://wescores.net

# 查看当前 release
readlink -f /var/www/stativo/current

# 查看所有 releases
ls -lt /var/www/stativo/releases/

# 查看磁盘空间
df -h

九、注意事项

  1. 必须先提交代码:git archive HEAD 只打包已提交代码,未提交的修改不会部署。部署前确保 git status 显示 “working tree clean”。

  2. .env.production 不在 Git 中:.gitignore 的 .env* 规则排除了它。部署脚本从 /tmp/.env.production 读取,每次部署都会上传本地版本。

  3. deploy-remote.sh 随代码一起部署:脚本在 scripts/ 目录下,git archive 会包含它。但实际执行的是上传到 /tmp/deploy-remote.sh 的版本。

  4. TypeScript 类型检查已临时关闭:next.config.ts 中 ignoreBuildErrors: true,遗留类型错误不阻断构建。修复完所有类型错误后改回 false。

  5. 端口统一为 3000:PM2、Nginx proxy_pass、Docker 配置全部使用 3000。

  6. HOSTNAME 强制 0.0.0.0:ecosystem.config.js 中强制设置,避免系统 HOSTNAME(如 ip-172-31-xx-xx)导致应用只绑定内网 IP,健康检查和 Nginx 代理失效。

十、已踩过的坑

问题原因解决方案
jose 模块缺失Next.js standalone 未自动追踪next.config.ts 添加 outputFileTracingIncludes
localhost:3001 泄露源码 defaultConfig 默认值被编译进产物运行时被 production 配置覆盖,不影响实际运行
.env.production 不同步.gitignore 排除,服务器旧版覆盖新版部署脚本改为优先从 /tmp/.env.production 读取
AUTH_TRUST_HOST 缺失NextAuth v5 生产环境必需已添加到 .env.production
HTTPS 502Nginx proxy_pass 端口与 PM2 不一致统一为 3000
PM2 启动失败日志目录权限sudo chown -R ec2-user:ec2-user /var/log/stativo
HOSTNAME 绑定内网 IP系统 HOSTNAME 环境变量覆盖ecosystem.config.js 强制 HOSTNAME=0.0.0.0
TypeScript 类型错误代码有类型不兼容问题临时关闭 ignoreBuildErrors,后续逐个修复
未提交代码未部署git archive HEAD 只含已提交代码部署前必须 git commit

十一、Docker 配置(备用)

项目在 .docker/ 目录下保留了完整的 Docker 部署配置,端口已统一为 3000,当前未启用。

11.1 文件清单

文件说明
.docker/Dockerfile多阶段构建(deps → builder → runner),Alpine + dumb-init
.docker/docker-compose.yml容器编排,含应用容器 + Nginx + Certbot(SSL profile)
.docker/env.dockerDocker 环境变量(从 .env.production 复制,不提交 Git)
.docker/env.example环境变量模板(已更新为 wescores.net 域名)
.docker/deploy.shDocker 一键部署脚本(git pull + docker compose)
.docker/init-ssl.shLet’s Encrypt SSL 证书首次签发脚本(6 步自动化)
.docker/nginx/nginx.confDocker Nginx 配置(HTTP 重定向 + HTTPS 反向代理 + 静态缓存)
.docker/deploy-guide.htmlDocker 部署可视化指南
.dockerignore排除依赖、构建产物、secrets

11.2 Dockerfile 多阶段构建说明

1
2
3
Stage 1: deps        → 安装全量依赖(含 devDependencies),BuildKit 缓存 pnpm store
Stage 2: builder     → 复制源码,pnpm run build:prod,BuildKit 缓存 .next/cache
Stage 3: runner     → Alpine + dumb-init,复制 standalone 产物,非 root 用户运行

关键设计:

  • BuildKit 缓存挂载:pnpm store 和 .next/cache 跨构建复用,增量构建快
  • 依赖层与源码层分离:改代码不重装依赖
  • standalone 模式:镜像不含 node_modules,仅 standalone 追踪的文件
  • outputFileTracingIncludes 已在 next.config.ts 配置 jose 模块
  • dumb-init 作为 PID 1,正确转发 SIGTERM 实现优雅退出

11.3 两种 Docker 部署模式

模式 A:纯容器(复用系统 Nginx + SSL)

Docker 只跑应用容器,SSL 和域名继续用系统 Nginx(与 PM2 方案一致)。

适用场景:服务器已有系统 Nginx + SSL 证书,只想把进程管理从 PM2 换成 Docker。

模式 B:全容器(Docker Nginx + Certbot 自动 SSL)

Docker 管理全部:应用容器 + Nginx 反向代理 + Let’s Encrypt 证书自动签发和续期。

适用场景:全新服务器,不想手动配 Nginx 和 SSL,一切交给 Docker Compose。

11.4 切换到 Docker 部署的完整步骤

前置准备

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
# 1. SSH 连接服务器
ssh -i "c:\Users\chang\.ssh\sg-xiangyu.pem" ec2-user@52.77.230.213

# 2. 安装 Docker + Docker Compose v2(如未安装)
sudo yum update -y
sudo yum install -y docker
sudo systemctl enable docker
sudo systemctl start docker
sudo usermod -aG docker ec2-user
# 重新登录使 docker 组生效
exit
ssh -i "c:\Users\chang\.ssh\sg-xiangyu.pem" ec2-user@52.77.230.213
docker --version  # 验证安装

# 3. git clone 项目(如服务器上没有)
cd ~
git clone <仓库地址> stativo
cd ~/stativo

创建环境变量

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
cd ~/stativo/.docker
cp env.example env.docker
vi env.docker
# 填入真实值,参考 .env.production(域名 wescores.net、API 地址、鉴权密钥等)
# 关键项:
#   NEXT_PUBLIC_SITE_URL=https://wescores.net
#   NEXTAUTH_URL=https://wescores.net
#   AUTH_TRUST_HOST=true
#   NEXTAUTH_SECRET=NIUGOyvfaUH2Aq5DA75E9+Lh/nUy7jxxrvK2qeHdLwg=
#   AUTH_GOOGLE_ID=428668096460-mdonea4qtvcoacvqru8vrvf8mkia9n75.apps.googleusercontent.com

停止 PM2(如从 PM2 方案切换)

1
2
3
4
pm2 delete stativo
pm2 save
# 可选:如不再使用 PM2,卸载
# npm uninstall -g pm2

模式 A:纯容器部署

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
cd ~/stativo

# 拉取最新代码
git pull origin main

# 构建并启动(不启用 SSL profile)
docker compose -f .docker/docker-compose.yml up -d --build

# 等待健康检查(约 40 秒)
docker compose -f .docker/docker-compose.yml ps
# 状态应显示 "healthy"

# 验证
curl -s -o /dev/null -w '%{http_code}' http://localhost:3000
# 应返回 200

系统 Nginx proxy_pass 指向 127.0.0.1:3000,与 PM2 方案一致,无需修改 Nginx 配置。

模式 B:全容器部署(含 SSL)

首次签发 SSL 证书:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
cd ~/stativo

# 确保域名 DNS 已指向服务器
dig wescores.net  # 应解析到 52.77.230.213

# 停止占用 80 端口的服务
sudo systemctl stop nginx  # 如有系统 Nginx

# 签发证书(6 步自动化)
bash .docker/init-ssl.sh
# 脚本会:
#   1. 停止现有容器
#   2. 创建 certbot 目录
#   3. 启动临时 Nginx 处理 ACME 验证
#   4. Certbot 签发证书
#   5. 清理临时 Nginx
#   6. 验证证书文件

启动完整部署:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
cd ~/stativo

# 启动(含 SSL profile:应用 + Nginx + Certbot)
docker compose -f .docker/docker-compose.yml --profile ssl up -d --build

# 查看容器状态
docker compose -f .docker/docker-compose.yml --profile ssl ps
# 应看到 3 个容器:stativo-web (healthy), stativo-nginx, stativo-certbot

# 验证
curl -s -o /dev/null -w '%{http_code}' https://wescores.net
# 应返回 200

SSL 证书自动续期:

Certbot 容器会每 12 小时自动检查并续期证书,无需手动操作。证书路径:

1
2
3
.docker/certbot/conf/live/wescores.net/
├── fullchain.pem
└── privkey.pem

11.5 Docker 日常更新部署

1
2
3
4
5
6
# 服务器上执行
cd ~/stativo
git pull origin main
docker compose -f .docker/docker-compose.yml up -d --build
# 或如使用 SSL profile:
# docker compose -f .docker/docker-compose.yml --profile ssl up -d --build

或从本地一键触发(使用 deploy.sh):

1
2
cd C:\projects\stativo
ssh -i "c:\Users\chang\.ssh\sg-xiangyu.pem" ec2-user@52.77.230.213 "cd ~/stativo && git pull origin main && docker compose -f .docker/docker-compose.yml up -d --build"

11.6 Docker 常用运维命令

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
# 查看容器状态
docker compose -f .docker/docker-compose.yml ps
# 含 SSL:docker compose -f .docker/docker-compose.yml --profile ssl ps

# 查看应用日志
docker compose -f .docker/docker-compose.yml logs -f stativo

# 重启应用容器
docker compose -f .docker/docker-compose.yml restart stativo

# 停止全部容器
docker compose -f .docker/docker-compose.yml down
# 含 SSL:docker compose -f .docker/docker-compose.yml --profile ssl down

# 重建(无缓存,解决构建缓存问题)
docker compose -f .docker/docker-compose.yml build --no-cache
docker compose -f .docker/docker-compose.yml up -d

# 进入容器调试
docker exec -it stativo-web sh

# 查看容器资源使用
docker stats stativo-web

# 清理构建缓存(释放磁盘)
docker builder prune -f

# 清理悬空镜像
docker image prune -f

11.7 Docker 与 PM2 方案对比

维度PM2(当前)Docker
服务器依赖pnpm + node + pm2 + curlDocker + Docker Compose
环境一致性服务器直接构建✅ 容器隔离,多环境一致
多实例✅ 4 实例集群❌ 单实例(需配 replicas 或 nginx upstream)
部署命令git archive + scp + sshgit pull + docker compose up
构建速度服务器全量 buildBuildKit 缓存,增量快
资源限制PM2 max_memory_restart✅ 内存/CPU 硬限制
日志管理pm2 logsdocker logs(自动轮转)
回滚previous 软链,秒级回滚切镜像 tag
SSL 管理手动配置系统 Nginx✅ Certbot 自动签发续期
部署方式打包上传(不依赖服务器 git)服务器 git pull(需仓库访问权限)

11.8 Docker 部署注意事项

  1. 端口冲突:模式 A 和 B 不能同时使用。模式 B 的 Nginx 容器会占用 80/443 端口,与系统 Nginx 冲突。

  2. env.docker 不提交 Git:.dockerignore 已排除 env.docker 和 .docker/env.docker,需要在服务器上手动创建。

  3. git pull 部署 vs git archive 部署:Docker 的 deploy.sh 使用 git pull,需要服务器有仓库访问权限(部署密钥或 HTTPS 认证)。PM2 方案用 git archive 打包上传,不需要服务器访问 Git 仓库。

  4. 数据卷:SSL 证书存在 .docker/certbot/conf/ 目录,Docker 容器重建不会丢失证书。但 docker compose down 删除容器后,目录文件仍在。

  5. 内存限制:docker-compose.yml 配置了 512M 内存上限。如应用 OOM,调整 deploy.resources.limits.memory。

  6. 从 PM2 迁移到 Docker:必须先 pm2 delete stativo 释放端口 3000,否则 Docker 容器端口映射会失败。

Licensed under CC BY-NC-SA 4.0
最后更新于 Sep 20, 2026 00:00 UTC
comments powered by Disqus