如何使用 docker 多阶段构建为开发和生产创建最佳镜像(NodeJs 应用程序示例)

2025-06-08

如何使用 docker 多阶段构建为开发和生产创建最佳镜像(NodeJs 应用程序示例)

Docker在过去几年中人气飙升。它是改变软件工程师和 DevOps 工程师工作方式的工具之一。从 Docker v 17.05 开始引入多阶段构建功能,这有助于摒弃使用阶段和目标的旧式构建器模式。本文讨论了如何利用Node.js 示例应用程序构建适用于开发/测试和生产环境的最佳镜像。docker multi-stage build

Docker 鲸鱼

Iron DoggyAndrew Bain拍摄的照片

先决条件

  • 您了解 docker 并了解基本的 docker 命令,例如 build、exec
  • 你了解 docker-compose(不是必需的)

Docker 多阶段构建简介

Docker 多阶段构建允许我们使用多条语句分阶段构建 Docker 镜像FROM。文件可以从一个阶段复制到另一个阶段。一个很好的例子是,一个294 MB 的Golang 1.13 官方镜像(即使使用 Alpine 也只有 123 MB)可能和你应用程序的 Go 可执行文件一样大。由于 Golang 编译后会生成一个可执行二进制文件,因此第一阶段可以对其进行编译,第二阶段可以生成一个 Alpine 镜像(5 MB)来运行该可执行文件。因此,如果你的 Go 应用程序二进制文件是 10 MB,那么你的镜像可以是 15 MB(10 MB 二进制文件 + 5 MB Alpine),而不是笨重的 294 MB 官方 Go 镜像或 123 MB Alpine Go 镜像。你也可以查看一个示例

另一个很好的例子是前端 JavaScript 应用,你可以使用包含 Node、Webpack 以及所有必要的 npm dev 依赖项的应用来构建该应用。在下一阶段,可以使用精简的 nginx apline 镜像来提供服务,这样体积会小得多。

以下是docker多阶段构建的官方信息:

使用多阶段构建,您可以在 Dockerfile 中使用多个 FROM 语句。每个 FROM 指令可以使用不同的基础构建,并且每个指令都会开启一个新的构建阶段。您可以选择性地将构建文件从一个阶段复制到另一个阶段,从而将不需要的所有内容保留在最终镜像中。

遗憾的是,并非所有 Go 语言都能像 Go 那样编译成可执行二进制文件。不过,您可以利用多阶段构建来制作更符合需求的 Docker 镜像。下面我们将通过一个开源 Node.js 应用程序示例来探讨如何实现这一点。

多阶段构建之前的问题

我们将看到一个 Node.js 应用示例,它是一个使用 Express 构建的货币转换器 API。目前,Dockerfile 和构建存在以下问题:

  1. Nodemon 已安装在生产环境中
  2. 当前 docker 镜像没有开发依赖项(运行npm install --production
  3. 图像尺寸可以变得更小(即使使用 alpine)

以下是当前Dockerfiledocker-compose.yml针对本地开发的内容:

Dockerfile

FROM node:12-alpine

WORKDIR /src
COPY package.json package-lock.json /src/
RUN npm install --production

COPY . /src

EXPOSE 8080

RUN npm config set unsafe-perm true
RUN npm install -g nodemon

CMD ["node", "index.js"]
Enter fullscreen mode Exit fullscreen mode

我们看到,nodemon即使在生产环境中也安装了它,但这在生产环境中是不必要的。另一个问题是它没有开发依赖项,因此无法在 docker 内部运行测试。

Docker Compose 文件

web:
  build: .
  volumes:
   - .:/src
  command: npm start
  ports:
    - "8080:8080"
  environment:
    NODE_ENV: dev
    VIRTUAL_HOST: 'currency.test'
    VIRTUAL_PORT: 8080
Enter fullscreen mode Exit fullscreen mode

不必担心,VIRTUAL_HOSTVIRTUAL_PORT是针对nginx 代理的。

当前图像大小

让我们看看运行后得到的图像有多大docker build . -t currency-api-original

165 Mb 的原始 docker 镜像

所以目前它是 165 Mb,希望我们也可以在此过程中减小它的大小。

多阶段构建解决方案

现在,由于我们希望在开发构建中拥有开发依赖项,nodemon而在生产构建中仅拥有生产 npm 依赖项,因此对 docker 相关文件进行了如下修改:

具有多阶段构建的 Dockerfile

FROM node:12-alpine as base

WORKDIR /src
COPY package.json package-lock.json /src/
COPY . /src
EXPOSE 8080

FROM base as production

ENV NODE_ENV=production
RUN npm install --production

CMD ["node", "index.js"]

FROM base as dev

ENV NODE_ENV=development
RUN npm config set unsafe-perm true && npm install -g nodemon
RUN npm install
CMD ["npm", "start"]
Enter fullscreen mode Exit fullscreen mode

让我们分析一下这里发生了什么变化以及为什么会发生?以下是重点内容:

  • 我们从一个有节点的基础镜像开始,然后将需要的文件复制到镜像中,如 1-5
  • 对于生产,我们将其设置NODE_ENV为生产并安装非开发依赖项,还要注意我们运行节点(而不是 nodemon)
  • 在 Dockefile 的最后 6 行中,我们从基础创建镜像并设置NODE_ENV为开发,然后我们安装 nodemon,因为我们想要在开发环境中查看文件
  • 在 dev 镜像构建时,我们安装所有 npm 依赖项,包括 dev 依赖项,以便我们可以运行测试

构建过程更加精简,我们也优化了 Docker 镜像,使其更加贴合环境。我们解决了上述问题,nodemon生产环境不再依赖开发环境,测试可以在开发/测试环境中运行。这真是太棒了!

多阶段构建后的 Docker-compose 文件

version: '3.5'
services:
  web:
    build:
      context: ./
      target: dev
    volumes:
    - .:/src
    command: npm start
    ports:
      - "8080:8080"
    environment:
      NODE_ENV: dev
      VIRTUAL_HOST: 'currency.test'
      VIRTUAL_PORT: 8080
Enter fullscreen mode Exit fullscreen mode

docker-compose 文件的主要变化在于target:dev构建参数。

所有更改都可以在此拉取请求中查看。我们来看看镜像现在有多大:

优化后的 docker 镜像更小

我们运行以下命令来构建开发和生产图像:

  • docker build . -t currency-api-dev --target=dev
  • docker build . -t currency-api-prod --target=production

我们已将旧镜像缩减约 25 MB,用于生产版本。这是因为我们移除了 nodemon 和一些用于生产的开发依赖项。即使是开发版本,也减少了约 21 MB。

结论

这里的重点是构建适合环境的 Docker 镜像,而多阶段构建正是解决这个问题的方法。您可以使用相同的概念通​​过 Composer 构建 PHP 镜像。例如,开发构建可以使用 xdebug 进行调试,生产构建可以默认启用 opcache。

当您需要针对不同环境使用不同的东西时,请选择 docker 多阶段构建并避免使用 3 个不同的 dockerfile。

链接:https://dev.to/geshan/how-to-use-docker-multi-stage-build-to-create-optimal-images-for-dev-and-production-4o82
PREV
只需一分钟即可修复,让您的 React 网站更加 Google 友好 🤝
NEXT
如何利用 Google Cloud Platform 和其他服务以 0 美元创办一家科技初创公司