Dockerfile 应该执行“npm install”和“npm run build”还是只复制这些文件?

Mik*_*ike 13 build node.js docker dockerfile devops

我对 Docker 有点陌生,并试图围绕其中的一些概念进行思考。

在很多教程和文章(实际上,几乎所有)中,这是一个典型的用于 create-react-app 和 nginx 配置的 Dockerfile:

# CRA
FROM node:alpine as build-deps
WORKDIR /usr/src/app
COPY package.json package-lock.json ./
RUN npm install
COPY . ./
RUN npm run build

# Nginx
FROM nginx:1.12-alpine
COPY --from=build-deps /usr/src/app/build /usr/share/nginx/html
EXPOSE 80
CMD ["nginx", "-g", "daemon off;"]
Run Code Online (Sandbox Code Playgroud)

假设一切都按预期进行,图像将是巨大的。

我有采取稍微不同的方法的想法。在npm install && npm run build本地运行,然后将其作为 Dockerfile:

FROM nginx:1.12-alpine
WORKDIR /opt/app-root

COPY ./nginx/nginx.conf /etc/nginx/
COPY ./build ./src/dist/

COPY ./node_modules .

USER 1001
EXPOSE 8080
ENTRYPOINT ["nginx", "-g", "daemon off;"]
Run Code Online (Sandbox Code Playgroud)

哪种方法更好?每当我跑步时docker build -t app-test:0.0.1 .,在我看来,第二种方法总是更快。

Bir*_*Lee 11

在容器内构建可保证可预测且可重现的构建工件。npm install在 macOS 和 Linux 上运行可以生成不同的node_modules,例如node-gyp

人们经常node_modules使用多阶段构建来构建(如果您尝试构建的实际容器不是 Node.js 应用程序)。也就是说,您实际的 nginx 应用程序本身并不依赖于 Node.js,而是依赖于node_modules目录及其包含的文件。所以我们node_modules在一个节点容器中生成,并将其复制到新容器(nginx)中。

因此,使用多阶段 Dockerfile 构建的每个人都将生成完全相同的容器。如果您在构建期间将本地复制node_modules到容器中,其他同事将无法预测node_modules.


B12*_*ter 6

\n

Dockerfile 应该执行 \xe2\x80\x9cnpm install\xe2\x80\x9d 和 \xe2\x80\x9cnpm run build\xe2\x80\x9d 还是应该只复制这些文件?

\n
\n

TL;DR:它应该始终在多阶段映像的“构建步骤”中执行所有必要的构建命令!

\n

长答案:

\n

在您发布的第一个示例“教程”Dockerfile 中,多阶段构建使用了通过多阶段构建,您可以丢弃在先前阶段创建的工件,只保留最终映像中真正需要的文件和更改。在这种情况下,安装的“dev”包不会复制到最终映像中,因此不会消耗任何空间。构建文件夹将仅包含运行时所需的代码和节点模块,而不包含构建第一步中编译项目所需的任何开发依赖项。

\n

在第二种方法中,您在npm install && npm run build外部运行Dockerfile,然后将结果复制到最终图像中。虽然这可行,但从 devops 的角度来看,这不是一个好主意,因为您希望将所有必需的构建指令一致地保留在一个位置(最好在一个 Dockerfile 中),因此构建镜像的下一个人不必弄清楚如何编译流程有效。从本地计算机复制构建结果的另一个问题是,您可能正在运行具有不同节点版本等的另一个操作系统,这可能会影响构建结果。相反,如果您像“教程”Dockerfile 一样在 Dockerfile 中进行构建,您就可以完全控制操作系统和环境(节点版本、node-sass 库等),并且每个执行者都将docker build获得相同的编译结果(鉴于您已确定 Dockerfile 基础映像的节点版本,即使用而FROM node:14.15.4-alpine as build-deps不是仅仅使用FROM node:alpine as build-deps)。

\n

关于 Dockerfile 演变的最后一点说明。在过去,实际上是在 Dockerfile 之外(或在另一个单独的 Dockerfile 中)执行编译,然后将所有结果复制到最终映像中的方法。这与您的OP中提到的第二种方法相匹配。但针对上述所有缺点,docker 架构师在 2017 年发明了多阶段构建。以下是来自docker 博客的一些有启发性的引述:

\n
\n

在多阶段构建之前,Docker 用户将使用脚本在主机上编译应用程序,然后使用 Dockerfile 来构建映像。然而,多阶段构建有助于创建小型且效率显着提高的容器,因为最终映像可以摆脱任何构建工具。[并且]不再需要外部脚本来编排构建

\n
\n

官方文档中重申了同样的想法:

\n
\n

实际上,使用一个 Dockerfile 用于开发(其中包含构建应用程序所需的所有内容)和一个用于生产的精简 Dockerfile(仅包含您的应用程序以及运行它所需的内容)是很常见的。这被称为\xe2\x80\x9cbuilder 模式\xe2\x80\x9d。维护两个 Dockerfile 并不理想。[\xe2\x80\xa6] 多阶段构建极大地简化了这种情况![\xe2\x80\xa6] 您只需要单个 Dockerfile。您也不需要\xe2\x80\x99 需要单独的构建脚本。只需运行 docker build 即可。最终结果是与以前相同的微小生产图像,但复杂性显着降低。您不需要创建任何中间映像,也不需要将任何工件提取到本地系统。

\n
\n