Skip to content

实战案例: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               │
└──────────────────────────────────────────────────────────────────────┘

开发流量路径与行为:

  1. 浏览器访问 bush.test,Traefik 根据 labels 转发到 bush_app_dev:8000
  2. 前端请求 /api/*,由 Umi proxy 直连 jungle_app_dev:3000
  3. 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,并非容器“独立的一份”。
    • 同时使用 .:/appapp_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 需纳入发布链路

数据与上传隔离

  • 优点:数据库只在内部网络暴露,上传文件通过宿主机目录集中管理
  • 代价:需要确保宿主机目录权限与备份策略

Released under the MIT License.