第 15 章 · 生产部署:Docker、Nginx 与 CI/CD
本章目标:用 多阶段 Dockerfile 构建 Vue/React SPA;配置 docker-compose + Nginx 反向代理;掌握运行时 环境变量注入 与 健康检查;编写 GitHub Actions / Gitea CI 流水线;理解 蓝绿/滚动发布(对接小紫云计算);在 frontend-web ch15 静态部署基础上深化容器化与自动化。
学时建议:4~5 小时
前置:frontend-web ch15(Vite 构建与静态托管)、本模块 ch14(CI 跑测试)。
15.1 从 ch15 静态部署到容器化
| 阶段 | frontend-web ch15 | 本章 frontend-framework ch15 |
|---|---|---|
| 构建 | npm run build → dist/ | 同上,在 Docker builder 阶段 |
| 托管 | 对象存储 / 静态 CDN | Nginx 容器 |
| 配置 | 手动上传 | CI 自动 build + push 镜像 |
| 多环境 | 多套 bucket | 镜像 tag + 环境变量 |
| 回滚 | 换静态文件版本 | 换镜像 tag / 蓝绿切换 |
开发者 push
│
▼
CI:lint → test → build → docker push
│
▼
小紫云 / K8s:滚动更新 Deployment
│
▼
Nginx Ingress → 商户后台 SPA
行业案例 · 贤紫优选商城:商户后台 2025 年从小紫云「单节点 Nginx + 手工 SCP」升级为 Git 触发 CI → Harbor 镜像 → K8s RollingUpdate,发布从 40 分钟降至 8 分钟,回滚一键 kubectl rollout undo。
15.2 多阶段 Dockerfile(Vue SPA)
# Dockerfile — academy-admin-vue
# syntax=docker/dockerfile:1
# ── Stage 1: 依赖 ──
FROM node:20-alpine AS deps
WORKDIR /app
COPY package.json package-lock.json ./
RUN npm ci
# ── Stage 2: 构建 ──
FROM node:20-alpine AS builder
WORKDIR /app
COPY --from=deps /app/node_modules ./node_modules
COPY . .
# 构建时注入 API 地址(也可用运行时注入,见 15.5)
ARG VITE_API_BASE_URL=https://api.xianzi.shop/v1
ENV VITE_API_BASE_URL=$VITE_API_BASE_URL
RUN npm run build
# ── Stage 3: 运行 ──
FROM nginx:1.27-alpine AS runner
COPY docker/nginx.conf /etc/nginx/conf.d/default.conf
COPY --from=builder /app/dist /usr/share/nginx/html
EXPOSE 80
HEALTHCHECK --interval=30s --timeout=3s --start-period=5s --retries=3 \
CMD wget -qO- http://127.0.0.1/health || exit 1
| 阶段 | 镜像体积贡献 | 说明 |
|---|---|---|
| deps | 不进入最终镜像 | 仅 node_modules |
| builder | 不进入最终镜像 | 产出 dist |
| runner | ~45MB | 仅 Nginx + 静态文件 |
docker build -t xianzi/academy-admin-vue:1.0.0 .
docker run -p 8080:80 xianzi/academy-admin-vue:1.0.0
15.3 React SPA Dockerfile(差异点)
与 Vue 相同三阶段;构建命令同为 npm run build,产出目录均为 dist/(Vite 默认)。
# 若使用 Create React App,产出为 build/
# COPY --from=builder /app/build /usr/share/nginx/html
.dockerignore(Vue/React 共用):
node_modules
dist
.git
coverage
*.md
.env.local
15.4 Nginx 配置:SPA 路由与健康检查
# docker/nginx.conf
server {
listen 80;
server_name _;
root /usr/share/nginx/html;
index index.html;
# 健康检查端点(配合 Docker HEALTHCHECK 与 K8s probe)
location = /health {
access_log off;
default_type text/plain;
return 200 'ok';
}
# 静态资源长缓存(Vite 带 hash)
location /assets/ {
expires 1y;
add_header Cache-Control "public, immutable";
}
# SPA history 模式:非文件请求回退 index.html
location / {
try_files $uri $uri/ /index.html;
}
# 可选:反向代理 API(生产也可由 Ingress 分流)
location /api/ {
proxy_pass http://backend:8000/api/;
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;
}
gzip on;
gzip_types text/css application/javascript application/json;
}
| 配置 | 作用 |
|---|---|
try_files ... /index.html | Vue Router / React Router history 模式必需 |
/assets/ 长缓存 | 利用 Vite 文件名 hash |
location /api/ | 同源代理,免 CORS(与 ch13 对照) |
15.5 docker-compose 本地联调
# docker-compose.yml
services:
admin-web:
build:
context: .
dockerfile: Dockerfile
args:
VITE_API_BASE_URL: http://localhost:8000/api/v1
ports:
- '8080:80'
depends_on:
backend:
condition: service_healthy
networks:
- xianzi-net
backend:
image: xianzi/mall-api:dev
ports:
- '8000:8000'
environment:
DATABASE_URL: postgres://...
healthcheck:
test: ['CMD', 'curl', '-f', 'http://localhost:8000/health']
interval: 10s
timeout: 3s
retries: 5
networks:
- xianzi-net
networks:
xianzi-net:
docker compose up --build
# 访问 http://localhost:8080
15.6 环境变量注入策略
| 方式 | 时机 | 适用 |
|---|---|---|
构建时 ARG VITE_* | docker build | 常量 API 域名,简单 |
运行时 env.js | 容器启动 entrypoint 写文件 | 同一镜像多环境 |
| K8s ConfigMap | Pod 挂载 | 小紫云标准做法 |
15.6.1 运行时注入(推荐多环境)
# docker/entrypoint.sh
#!/bin/sh
cat <<EOF > /usr/share/nginx/html/env-config.js
window.__ENV__ = {
API_BASE_URL: "${API_BASE_URL:-/api/v1}",
APP_NAME: "${APP_NAME:-贤紫商户后台}"
};
EOF
exec nginx -g 'daemon off;'
<!-- index.html -->
<script src="/env-config.js"></script>
// config/env.ts
export const env = {
apiBase: (window as any).__ENV__?.API_BASE_URL ?? import.meta.env.VITE_API_BASE_URL,
}
注意:VITE_ 变量在 npm run build 时被打包替换;运行时方案需读 window.__ENV__。