CHAPTER 9 · PRODUCTION DELIVERY
09
Vite Proxy 只存在于开发环境,正式上线后谁来转发 /api?
本地能运行,只说明开发链路打通了。正式上线时,前端、后端、数据库、配置、镜像和发布流程会变成另一套工程问题。
开发环境不等于正式环境
正式环境通常不会运行 npm run dev。
开发环境
浏览器
↓
Vite 开发服务器
├─ 返回 React 页面
└─ Proxy /api → Go 后端
正式环境
浏览器
↓
Nginx 或统一网关
├─ 返回静态前端文件
└─ /api → Go 后端
React 项目上线后,运行的到底是什么?
Go 项目上线后,运行的又是什么?
前端改一个按钮,是否必须重新发布后端?
后端改一个接口,是否必须重新构建前端?
开发环境的运行方式 ≠ 正式环境的部署方式
项目组织
代码放在同一个目录,不代表上线时是一个应用。
练习项目
album-system/
├── frontend/
└── backend/
方便一次打开完整代码,便于本地联调,也便于前后端接口对照。
正式项目常见形态
album-web
独立前端仓库
album-api
独立后端仓库
前端和后端可以拥有不同构建流程、发布节奏和回滚方式。
前后端是否放在同一个仓库,要看团队规模和工程管理方式。关键不在“目录是否分开”,而在它们应该被视为两个独立构建、独立运行、独立发布的应用。
构建产物
前端产物和后端产物不是一类东西。
静态文件
TypeScript + React 源码
↓ npm run build
dist/index.html
dist/assets/*.js
dist/assets/*.css
构建后通常不再需要 Node.js 执行业务代码,而是由 Web 服务器提供静态文件。
服务程序
Go 源码
↓ go build
可执行文件
↓ 持续运行
监听端口并处理 /api
后端产物是一个持续运行的服务,需要数据库、环境配置、日志和健康检查。
正式环境请求链路
Nginx 提供前端文件,也转发后端 API。
server {
listen 80;
server_name album.example.com;
root /usr/share/nginx/html;
index index.html;
location /api/ {
proxy_pass http://album-api:8080;
}
location / {
try_files $uri $uri/ /index.html;
}
}root
指定 React 构建产物所在目录。
location /api/
将 API 请求转发给 Go 后端。
try_files
解决 React 单页应用刷新子页面时出现 404 的问题。
Nginx 不是用来替代 Go 后端,而是作为静态资源服务器和统一请求入口。浏览器看到同一个域名,内部请求由 Nginx 分发。
先把 Nginx 跑起来
用 Docker 一条命令启动一个 Nginx。
先不急着写 Dockerfile。先用现成的 Nginx 镜像,把“静态文件目录”和“Nginx 配置”挂进去,让浏览器能访问到前端构建产物。
docker run -d \
--name album-nginx \
-p 8081:80 \
-v ./frontend/dist:/usr/share/nginx/html:ro \
-v ./nginx.conf:/etc/nginx/conf.d/default.conf:ro \
nginx:1.27-alpine-p 8081:80
浏览器访问本机 8081,Docker 转发到容器里的 Nginx 80 端口。冒号左边是本机端口,右边是容器端口。
-v ./frontend/dist:/usr/share/nginx/html:ro
把前端构建产物挂到 Nginx 默认静态目录。:ro 表示容器只读。
-v ./nginx.conf:/etc/nginx/conf.d/default.conf:ro
把本地 Nginx 配置挂进容器,用来控制前端路由和 /api 转发。
这个命令适合理解 Nginx 的运行方式,但它不是最终交付形态。正式发布时,更常见的做法是把构建产物和配置打进镜像,形成可版本化、可发布、可回滚的前端镜像。
Docker 镜像
为什么还需要 Dockerfile?
Dockerfile 是把“怎么构建、怎么运行”写成可重复的说明书。
刚才的 docker run 适合临时启动一个 Nginx,但正式项目不能靠手工记命令。Dockerfile 把安装依赖、构建产物、复制配置、选择运行环境这些步骤固定下来,CI/CD 才能每次构建出一致的镜像。
它回答的不是“这段代码怎么写”,而是:这个应用最终应该以什么环境、什么文件、什么命令运行。AI 可以生成 Dockerfile,但你必须能看懂它把哪些东西带进了最终镜像。
单阶段镜像可能包含
编译器
包管理工具
源代码
构建缓存
开发依赖
多阶段最终镜像只保留
前端静态文件 + Nginx
或后端可执行文件
必要运行环境
审查 Dockerfile
AI 可以生成配置,但你要知道最终镜像里有什么。
前端镜像
是否使用多阶段构建?是否生成 dist?最终镜像是否仍运行 npm run dev?Nginx 是否处理 /api 和前端路由刷新?
后端镜像
是否先复制 go.mod 和 go.sum?最终镜像是否包含 Go 编译器、源码、.env 或开发工具?
错误信号
前端正式镜像执行 npm run dev,说明它仍依赖开发服务器,不适合作为正式运行方式。
基础镜像版本
示例版本不能盲目复制,应根据项目实际锁定并维护基础镜像版本。
配置与镜像分离
同一个镜像,使用不同环境配置。
测试环境和生产环境数据库地址不同,不应该分别构建两个后端镜像。镜像负责“应用是什么”,运行时配置负责“应用连接哪里”。
/api 路径、应用公开名称
这是公开信息,可以出现在前端构建产物中。
JWT Secret、MySQL 密码
不可以前端产物会被浏览器下载,不能包含任何服务端秘密。
OSS AccessKeySecret、服务端密钥
不可以这类配置应由后端运行环境或密钥系统提供。
React 的 VITE_ 环境变量通常会在构建时写入前端产物,因此不能包含秘密。
独立发布
前后端分离的关键,是可以独立构建、发布和回滚。
修改前端按钮颜色
构建前端
↓
生成新前端镜像
↓
发布前端
通常不需要重新构建 Go 后端。
修复后端查询逻辑
构建后端
↓
生成新后端镜像
↓
发布后端
只要接口契约兼容,通常不需要重新构建前端。
独立发布也会带来兼容问题
删除字段、修改字段含义、修改数据类型、改变错误码,都可能影响尚未更新的前端。更安全的方式是新增字段而不是立即删除旧字段,新旧版本短期兼容,接口变更经过评审,并明确发布顺序。
CI/CD
发布不是执行几条命令,而是一条可重复的链路。
前端 CI/CD
提交前端代码
↓
安装依赖
↓
TypeScript 检查
↓
前端构建
↓
构建并推送镜像
↓
部署前端
后端 CI/CD
提交后端代码
↓
Go 格式和静态检查
↓
单元测试与编译
↓
构建并推送镜像
↓
数据库变更审批
↓
部署后端并健康检查
数据库变更边界
CI/CD 不能在服务启动时执行 GORM AutoMigrate。
正式服务禁止依赖 db.AutoMigrate(...) 修改表结构。
数据库变更必须显式、可审查、可回滚,不能隐藏在应用容器启动过程中。
Docker 镜像成功构建,不代表已经可以上线。上线还要考虑镜像版本、环境配置、数据库变更、健康检查、日志、回滚、密钥注入和前后端兼容。
给 AI 的工程约束
不要只说“生成一个 Dockerfile”。
请为当前项目生成正式环境构建方案。
项目情况:
- 前端使用 React、TypeScript 和 Vite
- 后端使用 Go 和 Gin
- 前端和后端分别构建 Docker 镜像
- 前端由 Nginx 提供静态资源
- Nginx 将 /api 转发到后端服务
- 浏览器不能直接访问后端内部端口
工程要求:
1. 前后端都使用多阶段 Docker 构建。
2. 尽量利用 Docker 构建缓存。
3. 最终镜像中不保留源码和编译工具。
4. 不把 .env、密码或 AccessKey 打入镜像。
5. 前端不能运行 Vite 开发服务器。
6. Nginx 需要处理 React 路由刷新。
7. Go 后端不使用 AutoMigrate。
8. 输出 Dockerfile、.dockerignore 和 Nginx 配置。
9. 解释每个关键工程决策。本章总结
- 练习项目同目录是为了本地开发方便,正式项目可以拆成独立仓库。
- 前端构建后得到静态文件,后端构建后得到可执行服务程序。
- 正式环境不运行 Vite 开发服务器。
- Nginx 可以提供前端静态文件并转发
/api。 - 前端和后端应该独立构建、发布和回滚。
- Dockerfile 应使用多阶段构建并减少最终镜像内容。
- 敏感配置不能写入前端、代码仓库或 Docker 镜像。
- CI/CD 将检查、测试、构建和部署过程自动化。
- 数据库变更必须显式、可审查,并经过审核。
- 正式服务禁止依赖 GORM AutoMigrate 修改表结构。
- AI 可以生成工程配置,但开发者必须审查架构、安全和发布边界。
服务已经可以发布了,真正上线后如何判断它是否健康?
下一章进入日志、健康检查、监控与故障定位。