慣性聚合 高效追讀感興趣之博客、新聞、科技資訊
閱原文 以慣性聚合開啟

推薦訂閱源

博客园 - 司徒正美
V
V2EX
T
Tailwind CSS Blog
有赞技术团队
有赞技术团队
aimingoo的专栏
aimingoo的专栏
Apple Machine Learning Research
Apple Machine Learning Research
IT之家
IT之家
Blog — PlanetScale
Blog — PlanetScale
A
About on SuperTechFans
月光博客
月光博客
T
The Blog of Author Tim Ferriss
宝玉的分享
宝玉的分享
Martin Fowler
Martin Fowler
博客园 - 聂微东
The GitHub Blog
The GitHub Blog
V
Visual Studio Blog
WordPress大学
WordPress大学
酷 壳 – CoolShell
酷 壳 – CoolShell
Engineering at Meta
Engineering at Meta
GbyAI
GbyAI

掘金

Win 安装Claude Code FastAPI 的 CORSMiddleware 跨域中间件 Java 自研 ReAct Agent 半年后,我用 LangGraph 验证了这些设计取舍 🚀AI编程工作流终极形态:GitNexus!零Token消耗实现代码知识图谱化!让Claude Code和Codex拥有上帝视角彻底告别盲目改代码,复杂项目重 LeetCode 72. 编辑距离:动态规划经典题解 被The Graph的GraphQL查询坑了三天,我用一个真实DeFi项目把链上数据索引彻底搞懂了 (AI) 编写简单 AI 助手 (ds-agent) 别再让 pnpm 跟着 nvm 跑了!独立安装终极指南 Claude Code 为什么这么顺?Anthropic 最新复盘:真正撑住它的不是模型,而是缓存 从 /simplify 指令深挖 Claude Code 多 Agent 协同机制 Function-Calling与工具使用 新手上路(六):Claude code装上ECC全家桶:38 个子代理、156 个技能、生产级 Hooks 与 Rules 体系 我在 Claude、Kimi、opencode 三个 AI 之间搭了一条自动协作管道 【技能篇】OpenClaw Skill 详解:给 AI 装上"专业外挂" wagmi v2 多链钱包切换:一个 Uniswap 仿盘项目让我踩了三天坑 两周浅学 RAG 我把 Python re 模块比喻成摸金手套 新手上路(三):Claude Code Skills 装了一堆没用?20+ 个 Skill 横向对比 + 三套组合方案,按需抄 K2.6、DeepSeek V4、GPT-5.5 都来了,组合拳打起来 Claude Code 进阶之路:从记忆系统到子代理编排 [java] 编译之后的记录类(Record Classes)长什么样子(上) 国产大模型能力大比拼,社区有话说 我研读了 500 个 Spring Boot 生产级代码库,90% 都犯了这 7 个致命错误 JAVA重点难点 转发-中央网信办部署开展“清朗·整治AI应用乱象”专项行动 合同同步逻辑 【合并已排序数组的三种实现策略,哪一种更可取?】 30天减20斤挑战:少一斤发100红包(2) 我竟然被JavaScript的隐式类型转换坑了三天! 二十五.Electron 初体验与进阶 本地到生产,解决 AI 全栈最后一公里——构建&部署&运维 程序员创业半年:顺的事、不顺的事,和我一直没想清楚的事 UI组件库elementplus 像使用 Redis 一样操作 LocalStorage 向量检索的流程是怎样的?Embedding 和 Rerank 各自的作用? LangChain DeepAgents 速通指南(七)—— DeepAgents使用Agent Skill 为什么越来越多的大厂抛弃MCP,转向CLI? 【节点】[SquareRoot节点]原理解析与实际应用 juejin.cn juejin.cn 从 “存得下” 到 “算得快”:工业物联网需要新一代时序数据平台越来越多工业用户开始意识到一个问题:**数据是存下来了, - 掘金 放弃 Claude 订阅?我用 8 年前的服务器,强跑 Google 最强开源模型 Gemma 4 真实测评! Python开发者狂喜!200+课时FastAPI全栈实战合集,10大模块持续更新中🔥 从 Claw-Code 看 AI 驱动的大型项目开发:2 人 + 10 个自治 Agent 如何产出 48K 行 Rust 代码 秒级创建实例,火山引擎 Milvus Serverless 让 AI Agent 开发更快更省火山引擎MilvusSer MediaPlayer 播放器架构:NuPlayer 的 Source/Decoder/Renderer 三驾马车 juejin.cn juejin.cn juejin.cn juejin.cn
自本地之开发至生产之部署:以 Docker Compose 调通 NestJS、MySQL 与 Milvus
swipe · 2026-05-23 · via 掘金

此文非独述 Docker 之概念,乃围绕实事项目而展开之部署教程也。吾辈将据今时之项目nest-dockerfile-test其真实源码,详述何以用 Docker Compose 提升本地开发之效,并如何将 NestJS 之服务、MySQL 之数据库共织为可运行之生产环境。

此文预设读者已略谙 JavaScript 或 TypeScript,然不须深究 Docker、Dockerfile、Docker Compose 或 NestJS 之部署。读毕,汝当明晓数事:

  • 何故后端项目不可独于本机?pnpm run start:dev动则毕矣。
  • Docker 镜像者,积存之基也;容器者,运行之体也;数据卷者,持久之器也;端口映射者,内外之桥也;容器网络者,互联之道也。各司其职,以解不同之困。
  • 何故本地开发环境常以 Compose 启动数据库与中间件,而业务代码仍于宿主机热更新运行?
  • 生产环境又何须将业务服务亦作镜像,并令业务容器通过容器名访问数据库容器?
  • 今项目之 Dockerfile、docker-compose.dev.ymldocker-compose.prod.yml 各段配置意何在?
  • 初学者当如何自零始,令项目于本地开发模式与生产部署模式皆能运行无碍?

本文所据,乃今之源码,非虚泛之模板。项目已含:

  • NestJS 11 之後端服务。
  • TypeORM + MySQL 之书籍 CRUD 接口。
  • /books 静态管理页面。
  • 本地开发所用也docker-compose.dev.yml
  • 生产部署所用的docker-compose.prod.yml
  • 以成 NestJS 镜像者Dockerfile
  • 用于镜像上下文裁剪者.dockerignore

需先明言:今之项目Dockerfile此乃单阶段构建,非多阶段构建也。单阶段之法,更易使初学者洞悉全貌,然于生产环境,犹可优化镜像之体。本文先就当前之实,详述其理,后于末章,补以生产优化之议。

一、何故后端项目必当慎处本地与部署之境?

初学者初涉 NestJS、Express 或 Spring Boot 之项目,最习以为常之启动法者:

pnpm install
pnpm run start:dev

但见控制台无报错,浏览器可通接口,便以为项目已然通矣。然真实之项目,其弊常不在“代码能否启动”,而在“代码所依之环境能否稳定复现”。

稍为完整之后端项目,非止一 HTTP 之服务。或更依:

  • MySQL:存用户、订单、书籍、权限、配置等核心业务之数据。
  • Redis:为缓存、分布式锁、验证码、会话、短期记忆之用。
  • Elasticsearch:为关键词检索与日志检索。
  • Milvus:为向量检索,常见于 RAG、知识库与语义搜索。
  • MinIO者,存对象之所也,文件、图像、音声、模型之产物皆可藏焉。
  • 消息队列者,为异步之务,平峰谷,解事件之耦。

于AI之应用开发,此等依存尤显。一RAG系统,或兼用MySQL存业务元数据,Milvus存向量,MinIO存原始文档,Redis存会话与任务之状。Agent系统亦常需数据库、中间件、任务队列与模型服务相协。是故,后端之码,不过系统之“调度层”,真正支撑业务运行者,乃代码、数据库与中间件所成之整套环境也。

若无Docker Compose,则本地开发常遇数典型之问题。

其一,环境安装之成本高。每有新同事,必手动安装MySQL,配置端口,建数据库,设字符集,复安装Milvus所倚之etcd与MinIO。但有一版不一致,其后便可能出现怪异之问题。

其次,境况难测。或人本地之MySQL为8.x,或为9.x;或人root之密码为admin,有者存焉123456;有者端口为3306,或因纷争易之3307启动之脚本,貌若相同,然所接之境,迥异殊别。

第三,部署之术与开发之法相离。于本地开发之际,服务所联者localhost,既登于服务器,则服务寓于器中。localhost遂为容器自体。若开时未通容器之络,则生产中最常之谬,乃业容器恒报。ECONNREFUSED 127.0.0.1:3306

Docker 與 Docker Compose 之價值在於此:其將環境由「仰人手而配置」變為「憑文檔而宣告」。一團隊但能維護好 Compose 文檔,新人僅需一令即可啟動諸依賴服務,而服務器亦僅需一令即可拉起業務與數據庫。

二、先明 Docker 核心之概念

於細究配置之前,先釐清 Docker 中易混淆之幾概念。初學者學習 Docker 之最大困難非命令之記憶,而在於不知每概念於系統鏈路中承擔何職責。

1. 鏡像:應用運行之唯讀範本

鏡像可視為一打包之運行範本。譬如 mysql:latest 為 MySQL 之鏡像,內含 MySQL 服務所需之程式與默認之文件結構。node:24-alpine 乃 Node.js 之镜像,内含 Node 运行时与 Alpine Linux 基础环境。

镜像本身不运行,惟为静态之物。可喻为“安装包+基础系统+默认配置”之合体。

当下项目之 Dockerfile ,旨在将 NestJS 项目构建为业务镜像。此镜像将含:

  • Node.js 运行时。
  • pnpm 包管理器。
  • 项目所依。
  • 编译后之 NestJS 代码。
  • 容器启动时执行之命令。

2. 容器:镜像运行后之实例。

容器者,镜像运行之后之进程境也。一镜可启多器,犹一类可创众象。

譬如尔可用之mysql:latest镜像启之,名曰mysql-dev其器,亦可行一谓名曰mysql-prod器也。其源一镜,然目录之属、器名、络道、端口之映,皆可殊异。

于今之项目:

  • 本地开发 Compose 则启动之mysql-devmilvus-etcdmilvus-miniomilvus-standalone
  • 生產 Compose 則啟動矣mysql-prodnest-app

3. 端口映射:容器之端口,曝于主机。

容器有自之网络名域。MySQL 在容器中监听 3306,非谓 Mac 或 Linux 之主机可直通之。欲使主机通容器之端口,须行端口映射:

ports:
  - '3306:3306'

左为主机端口,右为容器端口。'3306:3306'之意,乃访主机之 3306,转至容器内之 3306

NestJS 之服务亦然:

ports:
  - '3000:3000'

访主机之 http://localhost:3000,实入 nest-app 容器之内。3000端口。

四、数据卷:使容器之数据得久存

容器可删可建。若数据库之数据尽存于容器之内,则容器既删,数据亦隳。数据库此等有状态之服务,必将数据之目录挂载于宿主机。

今项目之MySQL开发环境配置者:

volumes:
  - ${DOCKER_VOLUME_DIRECTORY:-.}/volumes/mysql:/var/lib/mysql

右方/var/lib/mysql此乃 MySQL 容器内储数据之目录。左方${DOCKER_VOLUME_DIRECTORY:-.}/volumes/mysql此乃宿主之目录。如此,MySQL所写之数据,将落于项目之目录下。volumes/mysql,容器重建后犹可复用。

${DOCKER_VOLUME_DIRECTORY:-.}此乃 Compose 之变量语法。意谓:若环境变量DOCKER_VOLUME_DIRECTORY 有值,则用之;若无值,则用当前目录 .。是故数据目录既有默认之值,亦允尔于异机自定。

5. 容器网络:容器之间以服务名互访

Docker Compose 为同一 Compose 项目创制网络。同网络中之服务,可通过服务名互访。

于生产之境,NestJS 非以 localhost 访 MySQL,而以服务名 mysql-prod 访之:

host: isProduction ? 'mysql-prod' : 'localhost',

此行代码甚为枢要。盖因 NestJS 运行于容器之中时,localhost 指者 nest-app 容器自身,非 MySQL 容器,亦非宿主机。若续写localhost,业务容器自觅 MySQL,故自然连接不谐。

此异,乃众初以 Docker 部署后端服务时,最易罹之患也。

三、今项目之整体结构

先观项目结构中与部署相关之文:

nest-dockerfile-test
├── Dockerfile
├── .dockerignore
├── docker-compose.dev.yml
├── docker-compose.prod.yml
├── .env.example
├── package.json
├── nest-cli.json
├── public
│   └── index.html
└── src
    ├── main.ts
    ├── app.module.ts
    └── book
        ├── book.controller.ts
        ├── book.service.ts
        ├── dto
        │   ├── create-book.dto.ts
        │   └── update-book.dto.ts
        └── entities
            └── book.entity.ts

业务功能简矣:一为书籍管理之系统。后端供 /book CRUD 之接口,前端静态页面通过 fetch('/book') 调用后端之接口,数据存于 MySQL 之 books 表。

整体脉络,可由下图明之:

flowchart LR
  Browser["浏览器"]
  BooksPage["/books 静态页面"]
  Api["/book REST 接口"]
  Controller["BookController"]
  Service["BookService"]
  TypeORM["TypeORM EntityManager"]
  MySQL[("MySQL book 数据库")]

  Browser --> BooksPage
  BooksPage --> Api
  Api --> Controller
  Controller --> Service
  Service --> TypeORM
  TypeORM --> MySQL

开发与生产之部署方式,有所不同:

flowchart TB
  subgraph Dev["本地开发模式"]
    DevBrowser["浏览器"]
    DevNest["NestJS 在宿主机运行 pnpm run start:dev"]
    DevMysql[("mysql-dev 容器")]
    DevMilvus["Milvus 相关容器"]
    DevEtcd["etcd"]
    DevMinio["MinIO"]
    DevBrowser --> DevNest
    DevNest --> DevMysql
    DevMilvus --> DevEtcd
    DevMilvus --> DevMinio
  end

  subgraph Prod["生产 Compose 模式"]
    ProdBrowser["浏览器"]
    HostPort["宿主机 3000 端口"]
    NestContainer["nest-app 容器"]
    MysqlProd[("mysql-prod 容器")]
    ProdBrowser --> HostPort
    HostPort --> NestContainer
    NestContainer --> MysqlProd
  end

开发之要,在速效:数据库与间件,皆以器运行;业务之码,于主机以观模式运之,如是则改码立效。

生产之要,在一致:业务之码亦构为像,与 MySQL 共由 Compose 管之,服务重启、网络、端口、所恃关系,皆书于配置之文。

四、今之 NestJS 服务所为之事

部署之教,不可独言 Docker。必知器中所运者何服务,否则事出,不知所检其层。

项目之入口,乃 src/main.ts

import { NestFactory } from '@nestjs/core';
import { AppModule } from './app.module';

async function bootstrap() {
  const app = await NestFactory.create(AppModule);
  await app.listen(process.env.PORT ?? 3000);
}
bootstrap();

此段之码,为两事。

一,以 NestFactory.create(AppModule) 创建 Nest 应用之例。AppModule 乃应用之根本,内含注册靜態資源、數據庫連接、業務模塊。

次之,監聽端口。此處優先讀取 process.env.PORT,若未設置,則用 3000。此對部署至關重要,蓋因容器默認暴露者為 3000,生產 Compose 亦將宿主機 3000 映射至容器 3000

根模塊 src/app.module.ts 乃理解開發與生產之異同之關鍵:

const isProduction = process.env.NODE_ENV === 'production';

@Module({
  imports: [
    ServeStaticModule.forRoot({
      rootPath: join(__dirname, 'public'),
      serveRoot: '/books',
    }),
    TypeOrmModule.forRoot({
      type: 'mysql',
      host: isProduction ? 'mysql-prod' : 'localhost',
      port: 3306,
      username: 'root',
      password: 'admin',
      database: 'book',
      synchronize: true,
      logging: true,
      autoLoadEntities: true,
      entities: [Book],
    }),
    BookModule,
  ],
})
export class AppModule {}

此中有三點堪為詳述。

其一,ServeStaticModule.forRoot 將靜態頁面掛於 /books。編譯後,靜態文件將置於dist/public 运行之际,__dirname 指向 dist,故 join(__dirname, 'public') 乃能定位于 dist/public。此亦为 nest-cli.json 中必设 assets 拷贝之故,否则生产镜像中或仅有 JS,而无 public/index.html

至若 TypeOrmModule.forRoot 配置 MySQL 之连,开发时 NODE_ENV 非为 production,故 host 为 localhost。盖因开发模式中 NestJS 运于宿主,而 MySQL 容器通过端口映射现于宿主之 3306。生产时NODE_ENV=production,NestJS 运行于 nest-app 容器,此时须借 Compose 服务名 mysql-prod 以访数据库。

第三,synchronize: true 使 TypeORM 依 Entity 自动同步表结构。此法于 demo 与本地习之甚便,盖毋需手撰建表 SQL。然于真实生产项目,勿久启之。生产环境当以 migration 管理表结构之变,否则一调字段,或直损线上之数据。

书籍表结构定义于 Book Entity:

@Entity({ name: 'books' })
export class Book {
  @PrimaryGeneratedColumn()
  id: number;

  @Column({ length: 255 })
  title: string;

  @Column({ length: 255 })
  author: string;

  @Column({ type: 'text' })
  description: string;

  @Column({ type: 'decimal', precision: 10, scale: 2 })
  price: number;

  @Column({ type: 'int', default: 0 })
  stock: number;

  @Column({ type: 'datetime' })
  publishedAt: Date;

  @CreateDateColumn({ type: 'datetime' })
  createdAt: Date;

  @UpdateDateColumn({ type: 'datetime' })
  updatedAt: Date;
}

此 Entity 之用,在将 TypeScript 类映射为 MySQL 表。@Entity({ name: 'books' }) 指定表名,@PrimaryGeneratedColumn() 指定自增主键,@Column() 指定寻常字段,@CreateDateColumn@UpdateDateColumn 令 TypeORM 自行维护生成之时与更新之时。

此处 price 采用了 decimal(10, 2),此乃与金数相关字段更宜之策。浮点数易生精度之患,数据库中用 decimal 可避诸多不必要之金数误差。

事务逻辑在 BookService

@Injectable()
export class BookService {
  @Inject(EntityManager)
  private readonly entityManager: EntityManager;

  async create(createBookDto: CreateBookDto) {
    const book = this.entityManager.create(Book, {
      ...createBookDto,
      publishedAt: new Date(createBookDto.publishedAt),
    });
    return this.entityManager.save(Book, book);
  }

  async findAll() {
    return this.entityManager.find(Book, {
      order: { id: 'DESC' },
    });
  }

  async findOne(id: number) {
    const book = await this.entityManager.findOneBy(Book, { id });
    if (!book) {
      throw new NotFoundException(`Book #${id} not found`);
    }
    return book;
  }
}

此段代码于部署链路中位“业务容器内部之应用逻辑”。浏览器访 /book,请求先入 Nest Controller,再唤 Service,终由 TypeORM 访 MySQL。

publishedAt: new Date(createBookDto.publishedAt) 此转换亦堪注意。前段表单传至者乃字符串,如 "2008-08-01",数据库字段为 datetime。于保存之先,显式转为 Date,较之直将字符串付与 ORM,更为明晰。

前端页面在 public/index.html,非独立前端工程,乃由 NestJS 静态托举。核心调用之法如下:

const loadBooks = async () => {
  const response = await fetch('/book');
  const books = await response.json();
  renderRows(books);
};

form.addEventListener('submit', async (event) => {
  event.preventDefault();

  const id = inputs.id.value.trim();
  const payload = mapFormData();
  const method = id ? 'PATCH' : 'POST';
  const url = id ? `/book/${id}` : '/book';

  await fetch(url, {
    method,
    headers: { 'Content-Type': 'application/json' },
    body: JSON.stringify(payload),
  });

  await loadBooks();
});

此处用相对路径 /book,而非硬编码 http://localhost:3000/book。此举有部署之利:开发与生产环境,但页面与 API 同源,则无需处理跨域,亦不必于前端代码中区分不同 API 地址。

五、Dockerfile:如何将 NestJS 之项目化为镜像

者,今项目之Dockerfile若此:

ARG NODE_IMAGE=node:24-alpine
FROM ${NODE_IMAGE}

WORKDIR /app

COPY package.json pnpm-lock.yaml ./

RUN npm config set registry https://registry.npmmirror.com/ \
  && npm install -g pnpm@10.14.0 \
  && pnpm install --frozen-lockfile

COPY . .

RUN pnpm run build

EXPOSE 3000

CMD ["node", "dist/main.js"]

,须逐行观之。

ARG NODE_IMAGE=node:24-alpine者,定构建之参数也。其默认用node:24-alpine,然生产Compose时,可藉build args以覆之。今项目之.env.example,即示国内网络境下之镜像源例:

# Default Docker Hub images:
# NODE_IMAGE=node:24-alpine
# MYSQL_IMAGE=mysql:8.4

# 国内网络环境下的镜像源示例:
NODE_IMAGE=docker.m.daocloud.io/library/node:24-alpine

# 当前项目的 MySQL 使用华为云 SWR 镜像。
# 这个镜像当前按 linux/amd64 拉取,在 Apple Silicon 上会通过模拟方式运行。
MYSQL_IMAGE=swr.cn-north-4.myhuaweicloud.com/ddn-k8s/docker.io/mysql:8.4
MYSQL_PLATFORM=linux/amd64
MYSQL_HOST_PORT=3307

。此非业务之理,乃工程部署之容错设也。多机直引Docker Hub辄超时,许以环境变量易镜像源,可降部署之阶。今配置中,MySQL已迁华为云SWR镜像,复将宿主机端口默认易为3307,以避本机或开发境已占之3306

FROM ${NODE_IMAGE} 者,以 Node 镜像为基,构业务之镜像也。此中 Node 之版本,须与项目所恃相协。今项目用 NestJS 之十一、TypeScript 之五点七,且 package 内已备 @types/node 之二十四,故 node:24-alpine 乃相契。

WORKDIR /app 定容器内作业之域。后之 COPYRUNCMD 皆以 /app 为常所。如此则容器内之文脉井然,亦免令行于根域。

COPY package.json pnpm-lock.yaml ./ 但先抄依赖之单,不直抄源码之全。此 Docker 缓存优化之常法也。若依赖之文不改,则后之安装依赖层可复用缓存;汝但改业务之码,非必每度重装依赖。

pnpm install --frozen-lockfile 谓严循 pnpm-lock.yaml 而安其依。倘 lock 文与 package.json 不合,则安装弗成。此于建像为善,盖可避“吾地能安、伺器异之”之患。

COPY . . 复项目之码于像。此须配 .dockerignore ,否则 node_modules.gitvolumes 此等弗当入像之序,皆将混入,致建像迟缓且庞。

RUN pnpm run build 行 Nest 之编。今项目之 build 命为:

{
  "scripts": {
    "build": "nest build"
  }
}

。编毕,则生 dist 之序。盖 nest-cli.json 配 assets 故。public靜態文件亦將被拷貝至dist/public

{
  "compilerOptions": {
    "deleteOutDir": true,
    "assets": [
      {
        "include": "../public/**/*",
        "outDir": "dist/public"
      }
    ]
  }
}

若无此段配置,/books页面于本处试制或可无碍,然于生产之像中访问则或致败。其故者,TypeScript之编译,本唯理源码,不自发将任一静目录相携也。dist

EXPOSE 3000惟声明容器中所用之务也3000端口。此非自动暴露于宿主。使宿主得窥容器者,乃Compose中是也。ports

CMD ["node", "dist/main.js"]乃容器启动之命也。镜像构建之际,即行之。RUN器启而施之。CMD此二者当辨:构置之际,则编其码;运行之时,乃启其务。

今之Dockerfile,乃单阶段构建,故镜像中常存安装所依及构建所需之备。习之阶,如是更明,产之境,可进为多阶段构建:初阶,尽装所依并编;次阶,独存产依及 dist。然若未通单阶之流,勿遽求繁优化。

六、.dockerignore:制何物不纳镜像

今之项目,其 .dockerignore 为:

node_modules/
.vscode/
.git/
dist/
coverage/
volumes/
.env
.env.*
.tmp-*
*.log

此文件甚要。Docker构建时,以当前目录为build context,送诸Docker引擎。若不弃此诸物,将生数患。

一、node_modules 甚巨,且宿主之依,未必适于容器。譬如 macOS 上所装之二元依,不可径取入 Linux 容器。正道乃于镜像内重行安装。

第二,.git 无需入镜像。此将增构建上下文之体,或泄提交之息。

第三,volumes 乃数据库与中间件数据之目录,断不可复制入业务镜像。数据库数据当以 volume 挂载管理,不宜混入应用镜像。

第四,.env.env.* 或含密码、Token、密钥。今 demo 中密码甚简,然真项目里断不可将环境变量文件打入镜像。

第五,dist 当由镜像构建之过程所生。若将宿主机旧之 dist 复制而入,则易生“源码已改而镜像中运行者乃旧产物”之错觉。

七、本地开发之 Compose:一令即可唤起数据库与中间件之务。

今项目之本地开发 Compose 文件乃 docker-compose.dev.yml。其未启 NestJS 之应用,惟启 MySQL 与 Milvus 相关之基础服务。

此设计甚合宜。开发之际,冀改一行 TypeScript 之码即可热更新,故 NestJS 仍于宿主机上通过 pnpm run start:dev 运行。MySQL、Milvus 此类基础服务非须频改其码,置诸容器中统一管理即可。

开发环境 MySQL 之配置如下:

services:
  mysql:
    image: mysql:latest
    container_name: mysql-dev
    ports:
      - '3306:3306'
    environment:
      MYSQL_ROOT_PASSWORD: admin
      MYSQL_DATABASE: book
    command: mysqld --character-set-server=utf8mb4 --collation-server=utf8mb4_general_ci
    volumes:
      - ${DOCKER_VOLUME_DIRECTORY:-.}/volumes/mysql:/var/lib/mysql
    restart: always

此间MYSQL_ROOT_PASSWORD: admin将设root之密,MYSQL_DATABASE: book于MySQL初始化之际创book之库。NestJS中TypeORM所连之库名亦为book,故此二处必合。

command设默认字符集为utf8mb4。此于中文内容甚要。MySQL之初utf8非真全之UTF-8,utf8mb4方全支中文、emoji及更多Unicode之字。

Milvus于Compose之开发,由三务成:etcdminiostandalone。其关系可喻之:

flowchart LR
  Client["本地 AI 应用或脚本"]
  Milvus["Milvus standalone"]
  Etcd["etcd 元数据"]
  Minio["MinIO 对象存储"]

  Client --> Milvus
  Milvus --> Etcd
  Milvus --> Minio

Milvus 自身司向量化检索。etcd 用以存元数据、协信息,MinIO 用以存对象数据。尔今之 NestJS 书籍 CRUD 尚未直唤 Milvus,然开发之境已将 Milvus 编入,于后拓展 RAG 或语义检索,实为有益。

standalone 诸服务中,有两段配置甚为枢要:

environment:
  MINIO_REGION: us-east-1
  ETCD_ENDPOINTS: etcd:2379
  MINIO_ADDRESS: minio:9000
depends_on:
  - 'etcd'
  - 'minio'

ETCD_ENDPOINTS: etcd:2379 载明 Milvus 乃通过服务名 etcd 访 etcd。MINIO_ADDRESS: minio:9000 载明 Milvus 乃通过服务名 minio 访 MinIO。此即 Compose 网络之价值:容器间无需书宿主 IP,但书服务名。

文末定义了默认网络:

networks:
  default:
    name: common-network

此将创制或复用一网络,名曰common-network。统一网络名之利,在后续其他Compose项目若接入此网络,则能与此处中间件互通。然亦须留意,网络名既定,不同项目间或生命名与服务访问之耦合,团队中须明定章程。

八、本地开发从零启程之教

下为初学者可依循之本地开发流程。

1. 准备基础环境

汝需先装:

  • Docker Desktop或Docker Engine。
  • Node.js,建议用与项目相合之较新版本。
  • pnpm。

检视Docker是否可用:

docker version
docker compose version

验 Node 与 pnpm:

node -v
pnpm -v

docker compose version 可示版本,则知君用新式 Compose 插件,令文为 docker compose。旧篇之 docker-compose 乃旧令,非所必用。

2. 入项目之次第

cd "/Users/zz/AI learning/codeing/tool-test/nest-dockerfile-test"

若路径有隙,则令文须加引。初学者常于此失:AI learning 中有隙,若不加引,壳将之析为二参。

3. 装项目之依

pnpm install

此步所装,乃宿主之依。此依非 Dockerfile 中之 pnpm install --frozen-lockfile 同次所装。

  • 本地pnpm install乃为汝于宿主机运行也pnpm run start:dev
  • Dockerfile 之安装,为构生产镜像而设,俾容器内亦具运行项目所需之依。

四、启开发之基务

今朝package.json已备脚本矣。

{
  "scripts": {
    "docker:up": "docker compose -f docker-compose.dev.yml up -d",
    "docker:down": "docker compose -f docker-compose.dev.yml down"
  }
}

启程:

pnpm run docker:up

此令于暗处启 MySQL、etcd、MinIO、Milvus。初行则引镜,费时或久,尤 Milvus 之镜,其大甚也。

察容器之状

docker compose -f docker-compose.dev.yml ps

欲观日志者:

docker compose -f docker-compose.dev.yml logs -f mysql
docker compose -f docker-compose.dev.yml logs -f standalone

MySQL啟動成功後,宿主機者3306端口将接通mysql-dev器皿。Milvus 之常设,显:

  • 19530Milvus SDK 访问之端口。
  • 9091Milvus 健康检查之端口。
  • 9000MinIO API。
  • 9001MinIO 控制台。

五、启 NestJS 之务

开发 Compose 不得启动 NestJS,故尚需于宿主机行之:

pnpm run start:dev

启动之后,NestJS 将读取AppModule此配置之中。盖因此时未设也。NODE_ENV=production者,数据库之主也,乃localhost

host: isProduction ? 'mysql-prod' : 'localhost',

。是时localhost:3306,恰为MySQL之器映于主之端口,故连接得通。

。 六、浏览器访页

,启动既成,访

http://localhost:3000
http://localhost:3000/books

/,则返Hello World!/books启书籍管理之页。其间可增书、辑书、除书。

。 七、以curl验其接口

,增一书:

curl -X POST "http://localhost:3000/book" \
  -H "Content-Type: application/json" \
  -d '{
    "title": "Clean Code",
    "author": "Robert C. Martin",
    "description": "A handbook of agile software craftsmanship",
    "price": 99.9,
    "stock": 50,
    "publishedAt": "2008-08-01"
  }'

。查询全书:

curl -X GET "http://localhost:3000/book"

。查单条:

curl -X GET "http://localhost:3000/book/1"

新訂:

curl -X PATCH "http://localhost:3000/book/1" \
  -H "Content-Type: application/json" \
  -d '{
    "stock": 80,
    "price": 88.8
  }'

刪除:

curl -X DELETE "http://localhost:3000/book/1"

此套 CRUD 驗證全鏈:瀏覽器或 curl 發請求,NestJS Controller 接受請求,Service 呼叫 TypeORM,TypeORM 儲存至 MySQL,MySQL 資料藉 volume 持續於宿主機。

8. 停止本地開發環境

停止容器:

pnpm run docker:down

謹記,docker compose down將刪除容器與默認網絡,然不刪除掛載於宿主機的 volumes/mysql 資料目錄。故下次 pnpm run docker:up 後,資料猶存。

若欲徹底清空資料庫資料,須刪除相應宿主機目錄。此舉破壞性強,實際項目中需慎之又慎。

九、生产之Compose:将业务与数据库共织之序

本地调试之际,NestJS 运行于本机。生产之际,NestJS 当以容器运行。今项目之docker-compose.prod.yml此即为此目而备也。

services:
  mysql-prod:
    image: ${MYSQL_IMAGE:-mysql:8.4}
    platform: ${MYSQL_PLATFORM:-linux/amd64}
    container_name: mysql-prod
    environment:
      MYSQL_ROOT_PASSWORD: admin
      MYSQL_DATABASE: book
    ports:
      - '${MYSQL_HOST_PORT:-3307}:3306'
    command: mysqld --character-set-server=utf8mb4 --collation-server=utf8mb4_general_ci
    volumes:
      - ${DOCKER_VOLUME_DIRECTORY:-.}/volumes/mysql-prod:/var/lib/mysql
    restart: always

  nest-app:
    container_name: nest-app
    build:
      context: .
      dockerfile: Dockerfile
      args:
        NODE_IMAGE: ${NODE_IMAGE:-node:24-alpine}
    ports:
      - '3000:3000'
    environment:
      NODE_ENV: production
    depends_on:
      - mysql-prod
    restart: always

此卷仅载二事:mysql-prodnest-app不载 Milvus,盖今之产务链路,唯需 NestJS 与 MySQL耳。倘后之业务果需向量检索,则更宜于产用 Compose 中添入 Milvus,方为得宜。勿以开发之境有某间件,便谓产境必携之。产境每增一组件,即增一资源之耗、监控之费、故障之面。

mysql-prod与开發環境者mysql-dev颇类,然数据之录已易为:

volumes:
  - ${DOCKER_VOLUME_DIRECTORY:-.}/volumes/mysql-prod:/var/lib/mysql

此可避开发与生产之数据相淆。纵使于同一机演示,亦当别立开发、生产数据之目录。

此处尚有二事与镜像源相关。image: ${MYSQL_IMAGE:-mysql:8.4}表默认用 Docker Hub 之mysql:8.4,然若.env中设MYSQL_IMAGE,则改用.env中之镜像。今项目内MYSQL_IMAGE指华为云 SWR:

MYSQL_IMAGE=swr.cn-north-4.myhuaweicloud.com/ddn-k8s/docker.io/mysql:8.4
MYSQL_PLATFORM=linux/amd64
MYSQL_HOST_PORT=3307

platform: ${MYSQL_PLATFORM:-linux/amd64}乃为配此华为云 MySQL 镜像。其明示 Docker 拉取并运行 amd64 架构之镜像。若于 Apple Silicon 机器上运行,Docker Desktop 将以模拟方式运此容器。MYSQL_HOST_PORT=3307 则示宿主机访 MySQL 时用 localhost:3307,然容器内 MySQL 仍聆 3306

nest-appbuild,明其非直引成象,乃依 Dockerfile 于今项目目录构之:

build:
  context: .
  dockerfile: Dockerfile
  args:
    NODE_IMAGE: ${NODE_IMAGE:-node:24-alpine}

context: . 示建上下乃今项目根。dockerfile: Dockerfile 指建文。args 将 Compose 中 NODE_IMAGE 传 Dockerfile 中 ARG NODE_IMAGE

environment 设:

environment:
  NODE_ENV: production

此将影响。AppModule 之中,数据库之 host 判之:

const isProduction = process.env.NODE_ENV === 'production';
host: isProduction ? 'mysql-prod' : 'localhost',

。故生产容器既启,NestJS 便接 mysql-prod:3306。此址惟于 Compose 之网内方有义。今生产之 Compose,常将 MySQL 映于宿主机 3307,是故宿主机欲访 MySQL,则曰 localhost:3307;然容器欲访他容器,仍当用服名及容器内端口,即 mysql-prod:3306

depends_on 者,示 nest-app 之依 mysql-prod 也:

depends_on:
  - mysql-prod

。于此,须谨记一界:depends_on 仅可保启动之序,不能保 MySQL 已完全备妥。MySQL 容器既启,非即数据库可受连接。今之 demo 较简,重启后通常可通连接;真实生产,当为 MySQL 加 healthcheck,令应用有重试连接之能,或用待机脚本。

十、生产环境从零部署教程

今依初学者可行之式,使生产 Compose 运行。

1. 确勿与开发环境争端口

开发 Compose 映射 MySQL 至宿主机:3306

ports:
  - '3306:3306'

今生产 Compose 已改宿主机端口为默认:3307

ports:
  - '${MYSQL_HOST_PORT:-3307}:3306'

开发环境既如是mysql-dev 可据 3306,生产演繹之境 mysql-prod 可据 3307。若君不欲並運兩套 MySQL,至簡之法,仍先止發展之境:

pnpm run docker:down

。若君之機上 3307 亦被據,可於 .env 中續改:

MYSQL_HOST_PORT=3308

。此謂宿主以 3308 訪問生產 MySQL,然容器內部猶是 3306。慎諦 NestJS 容器訪問 mysql-prod:3306 不受此映,蓋容器間行內部之網,不經宿主映端口也。

2. 準備 .env

之项目,有.env.example

# Default Docker Hub images:
# NODE_IMAGE=node:24-alpine
# MYSQL_IMAGE=mysql:8.4

# 国内网络环境下的镜像源示例:
NODE_IMAGE=docker.m.daocloud.io/library/node:24-alpine

# 当前项目的 MySQL 使用华为云 SWR 镜像。
MYSQL_IMAGE=swr.cn-north-4.myhuaweicloud.com/ddn-k8s/docker.io/mysql:8.4
MYSQL_PLATFORM=linux/amd64
MYSQL_HOST_PORT=3307

若于国内网络境,引Docker Hub易败,可摹其本:

cp .env.example .env

Docker Compose常取今目录之.env。是故docker-compose.prod.yml中,${NODE_IMAGE:-node:24-alpine}${MYSQL_IMAGE:-mysql:8.4}${MYSQL_PLATFORM:-linux/amd64}${MYSQL_HOST_PORT:-3307}皆用.env之设。即华为云SWR镜像之用、MySQL宿主机端口之择,皆可弗改Compose之文,直由.env掌之。

于真实生产境,.env 不可提交于 Git。今 .dockerignore 已忽略 .env.env.*,此乃正道。

3. 构建并启动生产环境

项目 package.json 中备有生产启动之脚本:

{
  "scripts": {
    "docker:prod:up": "docker compose -f docker-compose.prod.yml up -d --build"
  }
}

执行:

pnpm run docker:prod:up

此令将行数事:

  1. 读取 docker-compose.prod.yml
  2. Dockerfile 而建 nest-app 之像。
  3. 拉起mysql-prod 为器。
  4. 而举 nest-app 之器。
  5. 背景运之,使诸务备。

若欲直用 Docker 之令,亦可行之:

docker compose -f docker-compose.prod.yml up -d --build

4. 观其务之状

docker compose -f docker-compose.prod.yml ps

当见 mysql-prodnest-app 二器。察其牍:

docker compose -f docker-compose.prod.yml logs -f mysql-prod
docker compose -f docker-compose.prod.yml logs -f nest-app

nest-app 牍中现 MySQL 之接误,当先察:

  • NODE_ENV 是否为 production
  • mysql-prod 之器是否已启而毕。
  • MYSQL_ROOT_PASSWORDMYSQL_DATABASE 是否与 TypeORM 之配置相合。
  • 二者是否同处一 Compose 之项目网络。

五、访生产之服务

生产之 Compose 将宿主机 3000 映射至 nest-app 容器之 3000。访问:

http://localhost:3000
http://localhost:3000/books

若在云服务器上,须将 localhost 易为服务器 IP 或域名:

http://服务器IP:3000/books

同时务须确保云服务器之安全组或防火墙放行 3000 端口。真实上线时,通常不直露之。3000,乃以 Nginx 或网关,将 80443 转发于内 3000

6. 验证 CRUD

,可继以 curl:

curl -X POST "http://localhost:3000/book" \
  -H "Content-Type: application/json" \
  -d '{
    "title": "Docker Compose 实战",
    "author": "Local Dev",
    "description": "从本地开发到生产部署的一本示例书",
    "price": 66.6,
    "stock": 20,
    "publishedAt": "2026-05-23"
  }'

再询:

curl "http://localhost:3000/book"

若得数据,则知生产链路已通:宿主机求入 nest-app 容器,NestJS 以 mysql-prod 服名访 MySQL,MySQL 书于挂载数据卷。

7. 止生产环境

docker compose -f docker-compose.prod.yml down

若惟欲重启:

docker compose -f docker-compose.prod.yml restart

若改代码欲重构:

docker compose -f docker-compose.prod.yml up -d --build

--build至要。无之,则Compose或直复旧像,汝以为码未效。

十一、将项目布于真机

本地生产Compose既通,上服务器之思无异,惟执行之境自汝之电脑易为云主。

一可行之部署序为:

  1. 备一Linux之机。
  2. 装Docker及Docker Compose插件。
  3. 将项目码上传于机。
  4. 于机上备.env
  5. docker compose -f docker-compose.prod.yml up -d --build
  6. 开端口或设逆代理。
  7. 配置記錄、備份、重啟之略策與監視。

设代码已上传于服务器之侧。/opt/nest-dockerfile-test

cd /opt/nest-dockerfile-test
cp .env.example .env
docker compose -f docker-compose.prod.yml up -d --build

察其状也。

docker compose -f docker-compose.prod.yml ps
docker compose -f docker-compose.prod.yml logs -f nest-app

若服务器用云商之安全组,须放行端口。学阶段可暂放之。3000,真实生产更宜:

  • 唯对外开80443
  • 以 Nginx 透传外求至本机3000
  • MySQL(米耶塞尔)者,3306不向公网开放。

nginx之反向代理简易之例:

server {
  listen 80;
  server_name example.com;

  location / {
    proxy_pass http://127.0.0.1:3000;
    proxy_set_header Host $host;
    proxy_set_header X-Real-IP $remote_addr;
    proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
  }
}

是故,用户求 http://example.com/books,nginx必转而达于主之 3000,复入于 nest-app 之器。

然有一界须明:今之compose,将mysql之端口默认映于主之 3307,以便于本机之调试。然于真实之公网服务器,数据库之端口不可直示于互联网。汝可删生产compose中mysql之 ports,独存器内之访问:

mysql-prod:
  image: ${MYSQL_IMAGE:-mysql:8.4}
  platform: ${MYSQL_PLATFORM:-linux/amd64}
  environment:
    MYSQL_ROOT_PASSWORD: admin
    MYSQL_DATABASE: book

既无 ports,则主之外部不可直通mysql,然nest-app 仍可通 Compose 内部之网,访 mysql-prod:3306。此乃更安之生产形态也。

十二、开发与生产之核心异同

多初学者问:既 Docker Compose 可启诸务,何不将 NestJS 亦纳于 Compose 以供本地开发?

答曰:可也,非必也。纳与否,视乎所重何在。

今项目采“开发仅容器化基设,业务代码本地运行”之式:

本地开发:
浏览器 -> 宿主机 NestJS -> localhost:3306 -> mysql-dev 容器

其利在开发体验佳。pnpm run start:dev 可 watch 文件之变,IDE 调试亦更直。其弊在宿主机需装 Node 与 pnpm。

生产环境则采“业务亦容器化”之式:

生产部署:
浏览器 -> 宿主机 3000 -> nest-app 容器 -> mysql-prod:3306 -> MySQL 容器

之长,在部署之境,更为一律。服务器无须手装项目所恃,但具 Docker,即可构作而启之。其短,在调试不若本地直运之便,镜像构作亦需时日。

若团队欲本地开发亦尽容器化,可更添一nest-dev之务,将源码悬于容器,于容器内运pnpm run start:dev。然此将引新患,如文件监听、所恃缓存、器内器外之权等。于学人及中小之务,今法更易解,亦更稳。

十三、今实中,堪注意之工界

1. synchronize: true 适于学,不适于严正之产

今 TypeORM 之配置有:

synchronize: true,

其能自依 Entity 同步表之构。学之阶甚便,惟书 Entity,表即自创。

然实之产,表构之变,须可审、可回、可灰。当用 TypeORM migration 或他数据库迁移之器。产境可易为:

synchronize: false,
migrationsRun: true,

乃以 migration 文件理字增、索变、数修。

2. 密码不宜死于源码与 Compose。

今之 demo 中,MySQL root 密码乃:admin

MYSQL_ROOT_PASSWORD: admin

TypeORM 中亦书:

password: 'admin',

学之项目可容,然实之项目当依环境变量注:

environment:
  MYSQL_ROOT_PASSWORD: ${MYSQL_ROOT_PASSWORD}

NestJS 中亦当读:

password: process.env.DB_PASSWORD,

密码不囿于代码仓,亦便诸境用异配置。

三、固 MySQL 版本,较之用 latest,尤宜于生产。

今之配置已避直用 mysql:latest,默认固于 MySQL 8.4:

image: ${MYSQL_IMAGE:-mysql:8.4}

同时,.env 中可易华为云 SWR 镜像:

MYSQL_IMAGE=swr.cn-north-4.myhuaweicloud.com/ddn-k8s/docker.io/mysql:8.4

此法较 mysql:latest 更稳。latest 随镜像仓之变而迁,今所引或一版,数月后重布所引或异版。固于 8.4 后,至少主版与小版明晰;更可通.env 更换华为云镜像源,可兼顾可复现与国内网络下载之成功率。

产制之际,更宜固定版本,如:

image: mysql:8.4

Node 镜像亦然。版本愈明,部署愈可复现。

4. depends_on 非健康检查,

depends_on 仅保启动次序,未保依赖之服务已就绪。MySQL 启动,常需数秒至数十秒。业务容器若启动过速,或致初连失败。

稳妥之法有二:

  • 为 MySQL 配置 healthcheck。
  • 在应用层数据库连接处开启重试。

例如 MySQL 可增:

healthcheck:
  test: ['CMD', 'mysqladmin', 'ping', '-h', 'localhost', '-padmin']
  interval: 10s
  timeout: 5s
  retries: 5

而后应用之层亦当容短暂败而重连。勿以服务启动之序为服务可用之验。

5. 当下 Dockerfile 尚可继以优化

当下单阶段 Dockerfile 易晓,然镜像将含构建所需之 devDependencies。生产可改为多阶段构建,例如:

ARG NODE_IMAGE=node:24-alpine

FROM ${NODE_IMAGE} AS builder
WORKDIR /app
COPY package.json pnpm-lock.yaml ./
RUN npm config set registry https://registry.npmmirror.com/ \
  && npm install -g pnpm@10.14.0 \
  && pnpm install --frozen-lockfile
COPY . .
RUN pnpm run build

FROM ${NODE_IMAGE} AS runner
WORKDIR /app
ENV NODE_ENV=production
COPY package.json pnpm-lock.yaml ./
RUN npm config set registry https://registry.npmmirror.com/ \
  && npm install -g pnpm@10.14.0 \
  && pnpm install --prod --frozen-lockfile
COPY --from=builder /app/dist ./dist
EXPOSE 3000
CMD ["node", "dist/main.js"]

多阶段构建之价值者:构建之阶段可安装完整依赖,运行之阶段仅存生产运行所需之物。镜像更微,攻击之面更微,部署传输亦更速。

然优化必建于理解之上。初学者先通当前单阶段之版,再行多阶段之改造,则易以定位问题。

十四、常见问题之排查

1. 端口 3306 被占用

现象:

Bind for 0.0.0.0:3306 failed: port is already allocated

缘常因本机已安装 MySQL,或开发 Compose 之故。mysql-dev既已运行,复启Compose之制。mysql-prod

處理之法:

docker ps
docker compose -f docker-compose.dev.yml down

今之生产Compose已过MYSQL_HOST_PORT=3307避开了開發 MySQL 之途3306若尔之器上3307亦被占,则续改之.env

MYSQL_HOST_PORT=3308

二、NestJS 之连 MySQL 失败

先辨当前运行之模。

若尔所行者pnpm run start:dev,NestJS 运行于宿主,当连接 localhost:3306。察之:

docker ps
docker compose -f docker-compose.dev.yml logs mysql

。若执行生产 Compose,NestJS 运行于容器,当连接 mysql-prod:3306。察之:

docker compose -f docker-compose.prod.yml logs -f nest-app
docker compose -f docker-compose.prod.yml logs -f mysql-prod

。尤须察 NODE_ENV 是否为 production。若生产容器无此环境变量,代码将走开发分支,试连容器之 localhost,终致失败。

3. /books 页面现 404 或空白

。首当察 nest-cli.json

"assets": [
  {
    "include": "../public/**/*",
    "outDir": "dist/public"
  }
]

。继而重构之:

pnpm run build

若容器访问不力,则重构其像。

docker compose -f docker-compose.prod.yml up -d --build

因靜態文件須入之dist/public复入镜像。惟更之。public/index.html然未复建,则产像自变不矣。

四、Docker 拉取镜像迟缓或失敗

可使用也.env.example镜像源配置于中

cp .env.example .env
docker compose -f docker-compose.prod.yml up -d --build

当前开发之Compose中,Milvus、etcd、MinIO之镜像地址,乃直书硬编码者也。若此等镜像拉取迟缓,须另行置换为可通之镜像源,或预先于网速优渥之境取之。

五、更易其码,然器中未显其效

生產容器行者,乃鏡像中所編之產,非自動讀宿主之源。改其碼後,需:

docker compose -f docker-compose.prod.yml up -d --build

若犹无效,可强令重筑之。

docker compose -f docker-compose.prod.yml build --no-cache nest-app
docker compose -f docker-compose.prod.yml up -d

六、数据不可删或恒存

此乃 volume 持久之常理也。docker compose down去容器,然不除宿主之目录。

volumes/mysql
volumes/mysql-prod

若欲涤除习得之数据,须先辍容器,继而刈其应配之录。实境之中,勿轻为之,盖此等行径,犹若刈断库之实据也。

十五、命速查

本地開發:

cd "/Users/zz/AI learning/codeing/tool-test/nest-dockerfile-test"
pnpm install
pnpm run docker:up
pnpm run start:dev

察开发之境器。

docker compose -f docker-compose.dev.yml ps
docker compose -f docker-compose.dev.yml logs -f mysql

辍其开物之境

pnpm run docker:down

制器启事

cp .env.example .env
pnpm run docker:prod:up

察生产之境。

docker compose -f docker-compose.prod.yml ps
docker compose -f docker-compose.prod.yml logs -f nest-app
docker compose -f docker-compose.prod.yml logs -f mysql-prod

停造生境

docker compose -f docker-compose.prod.yml down

重塑生之像:

docker compose -f docker-compose.prod.yml up -d --build

试其端:

curl "http://localhost:3000/book"

游其页:

http://localhost:3000/books

十六、何以继此项目而进

今之项目,实为研习 Docker Compose 之佳始,然非成之产之构。后可依序而进。

其一,使配置环境化。库之主、之口、之主名、之密、库名,皆当自环境变量取之,非固于 AppModule。可引 @nestjs/config,立 .env.development.env.production 之制。

其二,绝产之境synchronize,当以迁移代之。数据库之构,乃系系统之资,不可使 ORM 自行臆测而更之。

,次则优化 Dockerfile,为多阶段构建,固 Node、MySQL 之镜像版本。如此,可减镜像之体,增可复现之性。

,四则于 MySQL 与 NestJS 增 healthcheck。Compose 非编排之台,然基本健康之检,犹能升部署之信。

,五则引入 Nginx 与 HTTPS。真实之用,不当直叩 3000,对外宜为域名与 HTTPS。

,六则立备份之策。MySQL 数据挂载于宿主之目录,非谓其天然安。生产需定期备份、异地备份及恢复之演。

第七步,若业初用Milvus,乃纳Milvus于生产Compose,明其目录之所在、备份之方略、资源之限、监控之道。勿以本地开发有Milvus,便臆断线上亦必启之。

十七、总括

Docker Compose所解,非“如何行一令”之简,乃固一组服务之运:镜以何版,器以何名,端口若何映,数据置何处,服务互若何访,重启之策为何。

于今NestJS之业,本地开发以Compose启MySQL与Milvus,业码于宿主机以pnpm run start:dev热更行。此法兼得依环之同与开发调试之效。

生产之境,则用docker-compose.prod.yml同编之。mysql-prodnest-app。NestJS 乃以 NODE_ENV=production 易其数据库之主,自开发之 localhost 转为生产之 mysql-prod。此乃容器化部署之要义:容器内之 localhost 非宿主机,亦非他容器;容器间当以 Compose 之服务名相通信。

若为初学者,当依本文之序而践之:先明项目之接口与数据库之脉络,次运本地开发之 Compose,终行生产之 Compose。但能明晰“请求自浏览器至 NestJS,复至 TypeORM,终抵 MySQL”之径,亦能阐明“开发模式与生产模式数据库主为何异”,则已越 Docker Compose 部署后端项目之至要门槛矣。