实战案例:Bush & Jungle 容器化实践
本节以真实项目 Bush(React 前端)和 Jungle(NestJS 后端)为例,展示完整的容器化开发和部署方案,实现了开发态与生产态一致的容器化交付路径,同时通过 Traefik 统一入口、内部网络隔离、文件共享等机制,提高了项目整体的安全性与可维护性。
Docker 基础概念
Docker 通过容器技术把应用及其依赖打包成可迁移的运行单元,核心目标是“环境一致”,从而做到一次构建、多处运行。
- 镜像(Image):只读的应用模板(可分发、可复现)。
- 容器(Container):镜像的运行实例(本质是宿主机上的一组隔离进程)。
- Dockerfile:描述如何构建镜像的脚本(把依赖安装、构建、启动方式固化)。
- Docker Compose:多容器编排工具(把 Traefik、前端、后端、数据库、缓存等以一套配置一起拉起,并声明网络/卷等依赖关系)。
角色与边界
- Traefik 作为统一入口,负责域名路由、TLS 与统一安全中间件
- Bush 负责前端页面,开发态由 Umi Dev Server 承载,生产由 Nginx 静态服务承载
- Jungle 提供 API 服务,数据库与缓存只在内部网络可见
- 上传文件通过宿主机目录共享,实现前后端一致访问路径
开发环境架构
开发环境以 Traefik 为入口,容器通过 traefik_network_dev 互通,Jungle 额外使用 jungle_internal 承载数据库与缓存。
┌──────────────────────────────────────────────────────────────────────┐
│ Docker 网络:traefik_network_dev + jungle_internal │
│ │
│ http://bush.test → Traefik → bush_app_dev:8000 │
│ http://api.test → Traefik → jungle_app_dev:3000 │
│ │
│ ┌───────────────────────┐ ┌────────────────────────────┐ │
│ │ Bush bush_app_dev │ API │ Jungle jungle_app_dev │ │
│ │ node:20-alpine │───────►│ docker.1ms.run/node:20 │ │
│ │ Umi Dev Server │ │ NestJS --watch │ │
│ │ traefik_network_dev │ │ traefik_network_dev │ │
│ │ │ │ + jungle_internal │ │
│ └───────────────────────┘ └──────────┬─────────────────┘ │
│ │ │
│ │ │
│ ┌─────────▼─────────┐ │
│ │ Postgres & Redis │ │
│ │ (jungle_internal) │ │
│ └───────────────────┘ │
│ │
│ Volumes: app_node_modules / postgres_data / redis_data │
└──────────────────────────────────────────────────────────────────────┘开发流量路径与行为:
- 浏览器访问
bush.test,Traefik 根据 labels 转发到bush_app_dev:8000 - 前端请求
/api/*,由 Umi proxy 直连jungle_app_dev:3000 - Jungle 通过
jungle_internal访问 PostgreSQL 与 Redis
生产环境架构
生产环境同样通过 Traefik 统一入口,Bush 与 Jungle 只暴露内部端口,并在 traefik_network 上注册路由。数据库与缓存继续保持内部网络隔离。
用户 → Traefik(80/443)
├─ bush.1px.club → bush_app:80 (Nginx)
│ ├─ / → /usr/share/nginx/html
│ ├─ /api/ → jungle_app:3000
│ └─ /static/ → /app/shared_uploads
└─ api.1px.club → jungle_app:3000
jungle_app → postgres/redis (jungle_internal)上传文件共享:
- Jungle 容器挂载
/root/jungle_uploads到/app/uploads - Bush 容器挂载同一宿主机目录到
/app/shared_uploads
配置要点
1. 网络与服务发现
- 网络隔离:
traefik_network_dev/traefik_network:用于 Traefik 与应用容器通信。jungle_internal:仅用于后端与数据库/缓存通信,不对外暴露。
- Traefik Labels:
- 通过
traefik.http.routers.*定义域名路由(如bush.test)。 - 通过
traefik.http.services.*.loadbalancer.server.port指定容器内服务端口。
- 通过
2. 卷(Volumes)管理
- 命名卷是什么:由 Docker 管理的持久化存储,用“名字”引用而不是绑定宿主机固定路径;容器删除/重建后数据仍在,除非显式删除卷(如
docker compose down -v)。 - node_modules 隔离:使用命名卷(如
app_node_modules)挂载到/app/node_modules,避免宿主机与容器因系统架构差异(MacOS vs Linux)导致的二进制依赖冲突。 - 如何理解“映射/一致性”:
- 不存在“容器里构建 node_modules,宿主机会自动同步”的默认机制;是否同步取决于是否把宿主机路径挂载进容器。
- 若使用绑定挂载
.:/app,容器看到的/app本质就是宿主机项目目录;此时/app/node_modules通常就是宿主机的node_modules,并非容器“独立的一份”。 - 同时使用
.:/app和app_node_modules:/app/node_modules时,命名卷会覆盖/app/node_modules这个子路径:容器实际使用的是命名卷中的依赖,与宿主机node_modules解耦,双方增删改互不影响。
- 持久化数据:Postgres 和 Redis 数据挂载到命名卷。
- 文件共享:宿主机目录
/root/jungle_uploads同时挂载到前后端容器,实现文件上传与访问共享。
3. 容器前台运行
容器生命周期由 PID 1 主进程决定,主进程退出容器就会停止;因此像 Nginx 需要显式前台运行(如 daemon off),而 Postgres、Redis、Node 等默认就是前台进程,确保主进程不退出即可保持容器持续运行。
Dockerfile 差异
- Bush 生产镜像只复制
dist/,避免服务器构建导致 OOM - Jungle 生产镜像在容器内
pnpm run build,并以start:prod启动 - 开发环境统一使用
Dockerfile.dev,配合挂载与 node_modules 卷提升热更新体验
Nginx 反向代理与文件服务
nginx
server {
listen 80;
server_name 1px.club;
location /api/ {
proxy_pass http://jungle_app:3000/;
}
location /static/ {
alias /app/shared_uploads/;
}
location / {
try_files $uri $uri/ /index.html;
}
}开发流程
bash
cd traefik
docker compose -f docker-compose.dev.yml up -d
cd jungle
pnpm c:up
cd bush
pnpm c:up访问方式:
- 前端:
http://bush.test - 后端:
http://api.test
生产部署流程
bash
cd bush
pnpm deploy:local
docker compose up -d --build
cd jungle
docker compose up -d --build关键实践与取舍
统一网关入口(Traefik)
- 优点:路由与证书自动化、统一安全策略、服务自动发现
- 代价:需要维护 labels 与外部网络,对团队运维规范要求更高
生产构建前移(Bush)
- 优点:部署镜像更轻,避免服务器低配构建失败
- 代价:需要保证本地构建与部署流程一致,dist 需纳入发布链路
数据与上传隔离
- 优点:数据库只在内部网络暴露,上传文件通过宿主机目录集中管理
- 代价:需要确保宿主机目录权限与备份策略