关于Dockerfile多阶段构建: 示例 Dockerfile
FROM node:hydrogen-alpine AS builder
ARG GITLAB_TOKEN
# update and install dependency for build stage
RUN apk update && apk upgrade
RUN apk add git
# copy source code into image, note .dockerignore
WORKDIR /app
COPY . .
# create temp npmrc with npm auth token, install dependencies, prune for production
RUN rm -rf ./node_modules/*
RUN npm cache clean --force
RUN cp /app/.npmrc.local /app/.npmrc
RUN npm ci
RUN npm run postinstall
RUN rm /app/.npmrc
RUN npm run build
RUN npm prune --production
FROM node:hydrogen-alpine
WORKDIR /app
# copy from build stage
COPY --from=builder /app/package.json ./
COPY --from=builder /app/package-lock.json ./
COPY --from=builder /app/ecosystem.config.cjs ./
COPY --from=builder /app/.output ./.output
COPY --from=builder /app/node_modules ./node_modules
# COPY --from=builder /app/ci-scripts/entrypoint.sh ./entrypoint.sh
# RUN apk add bash
# RUN chmod +x /app/entrypoint.sh
# ENTRYPOINT ["/bin/bash", "/app/entrypoint.sh"]
# install PM2 process manager
RUN npm install pm2 -g
# install git and nginx
# RUN apk add nginx
# COPY --from=builder /app/nginx-template.conf /etc/nginx/templates/nginx-template.conf
ENV NITRO_PORT=3000
ENV NITRO_HOST=0.0.0.0
EXPOSE 3000
# start app with PM2 process manager
CMD [ "pm2-runtime", "/app/ecosystem.config.cjs"]
脚本分析: 这个 Dockerfile 使用了多阶段构建(multi-stage build)的方式,这是一个常见的 Docker 构建模式。它包含两个主要阶段:
构建阶段(Builder Stage):
- 这个阶段用于构建应用程序。它使用
node:hydrogen-alpine作为基础镜像,执行以下操作:- 安装构建所需的依赖项,如
git。 - 复制源代码到镜像中。
- 安装 npm 依赖并执行构建命令。
- 优化最终的依赖项,只保留生产所需的包。
- 安装构建所需的依赖项,如
- 这个阶段的输出是一个优化过的构建结果,包括应用程序代码和生产依赖项。
- 这个阶段用于构建应用程序。它使用
运行阶段(Runtime Stage):
- 这个阶段用于运行应用程序。它同样使用
node:hydrogen-alpine作为基础镜像,但不再包含构建时的工具和依赖项。 - 它从构建阶段复制所需的文件,如
package.json、package-lock.json、应用程序输出文件和node_modules文件夹。 - 安装
pm2进程管理器,用于运行应用程序。 - 设置运行时的环境变量,如
NITRO_PORT和NITRO_HOST。 - 最终,使用
pm2-runtime命令启动应用程序。
- 这个阶段用于运行应用程序。它同样使用
这种多阶段构建的好处是:
- 优化镜像大小: 构建阶段包含了所有构建依赖项,但在运行阶段,只有应用程序运行所需的文件和依赖项被复制到最终镜像中,减小了镜像体积。
- 分离构建和运行: 构建和运行阶段被分离,使得每个阶段都能专注于自己的任务,提高了灵活性和可维护性。
总之,这个 Dockerfile 采用了多阶段构建的模式,将构建和运行阶段分离,以优化最终 Docker 镜像的大小和结构。
多阶段构建中 AS builder的作用: 在多阶段构建中使用 AS <name> 语法是可选的,但通常被认为是一种最佳实践。
在这个 Dockerfile 中,使用 AS builder 是为了给第一个构建阶段命名为 "builder"。这样做有以下好处:
可读性: 给每个阶段起名字可以让 Dockerfile 更加清晰易懂。在后续的
COPY --from=builder语句中,可以清楚地知道它是从哪个阶段复制文件。复用性: 如果在 Dockerfile 中需要多次引用同一个构建阶段,使用命名可以让引用更加简单直观。
可维护性: 当 Dockerfile 变得复杂时,给构建阶段命名可以让代码更加模块化和可读。这有助于理解 Dockerfile 的结构和工作原理。
所以虽然 AS <name> 语法不是必须的,但它通常被认为是一种良好的实践,可以提高 Dockerfile 的可读性和可维护性。但如果你的 Dockerfile 非常简单,不使用命名也是可以的。
怎么知道是Runtime阶段: 在多阶段构建的 Dockerfile 中,通常最后一个 FROM 语句所定义的阶段就是运行时阶段(Runtime Stage)。
让我们来看看这个 Dockerfile 的结构:
- 第一个
FROM语句使用node:hydrogen-alpine作为基础镜像,并标记为AS builder。这是构建阶段(Builder Stage)。 - 最后一个
FROM语句同样使用node:hydrogen-alpine作为基础镜像。这个没有任何AS标记,所以它就是最终的运行时阶段(Runtime Stage)。
通常情况下,多阶段构建的最后一个阶段就是运行时阶段。这个阶段只包含应用程序运行所需的内容,而不包含构建工具和依赖项。
所以在这个 Dockerfile 中,当看到最后一个 FROM 语句使用 node:hydrogen-alpine 作为基础镜像时,我们就可以确定这就是运行时阶段。
总的来说,通过观察 Dockerfile 中 FROM 语句的顺序和是否有 AS 标记,可以很容易地识别出构建阶段和运行时阶段。最后一个 FROM 语句定义的就是最终的运行时阶段。
RUN npm ci的作用: RUN npm ci 是这个 Dockerfile 中的一个重要命令,它的作用是安装应用程序的依赖项。让我们更详细地解释这条命令:
RUN: 这是 Dockerfile 中的一个关键词,它表示在构建镜像过程中需要执行的命令。npm ci: 这是 npm 命令行工具提供的一个子命令。ci代表 "clean install",它与常见的npm install命令有一些区别:npm ci会严格按照package-lock.json文件中的版本信息安装依赖项,而不会自动升级依赖的版本。这有助于确保构建的可重复性。npm ci会首先删除node_modules文件夹,然后从头开始安装依赖项。这确保了一个干净的依赖安装环境。npm ci比npm install更快,因为它省略了一些额外的检查和处理步骤。
综合起来,在 Dockerfile 的构建阶段使用 RUN npm ci 可以确保应用程序的依赖项被可靠地安装,并且构建过程更加高效和可重复。这有助于确保最终生成的 Docker 镜像的一致性和可靠性。
这个脚本在执行 ci 之前先执行了 RUN rm -rf ./node_modules/*的作用: 在运行 npm ci 命令之前,Dockerfile 中有一行 RUN rm -rf ./node_modules/*。这个命令的作用是:
- 删除 -
rm命令用于删除文件或目录。 - 递归删除 -
-rf选项表示递归地删除目录及其内容。 - ./node_modules/* - 这个路径指的是删除
/app/node_modules目录下的所有内容。
为什么要在运行 npm ci 之前先删除 node_modules 目录呢?主要有以下几个原因:
清理环境 - 删除旧的
node_modules文件夹可以确保在安装依赖时从一个干净的环境开始,避免遗留的依赖项干扰新的安装过程。强制重新安装 - 通过删除
node_modules,强制npm ci命令从头开始安装所有依赖项,而不是尝试使用缓存的依赖项。这有助于确保完整和正确的依赖安装。镜像大小优化 - 在多阶段构建中,删除旧的
node_modules可以减小最终 Docker 镜像的体积,因为只有新安装的生产依赖项会被复制到运行时镜像中。
总之,这行 RUN rm -rf ./node_modules/* 命令是为了确保在安装依赖项之前,构建环境是干净和一致的,有助于提高构建过程的可靠性和最终镜像的质量。是的,你注意到了这个细节。在运行 npm ci 命令之前,Dockerfile 中有一行 RUN rm -rf ./node_modules/*。这个命令的作用是:
- 删除 -
rm命令用于删除文件或目录。 - 递归删除 -
-rf选项表示递归地删除目录及其内容。 - ./node_modules/* - 这个路径指的是删除
/app/node_modules目录下的所有内容。
为什么要在运行 npm ci 之前先删除 node_modules 目录呢?主要有以下几个原因:
清理环境 - 删除旧的
node_modules文件夹可以确保在安装依赖时从一个干净的环境开始,避免遗留的依赖项干扰新的安装过程。强制重新安装 - 通过删除
node_modules,强制npm ci命令从头开始安装所有依赖项,而不是尝试使用缓存的依赖项。这有助于确保完整和正确的依赖安装。镜像大小优化 - 在多阶段构建中,删除旧的
node_modules可以减小最终 Docker 镜像的体积,因为只有新安装的生产依赖项会被复制到运行时镜像中。
总之,这行 RUN rm -rf ./node_modules/* 命令是为了确保在安装依赖项之前,构建环境是干净和一致的,有助于提高构建过程的可靠性和最终镜像的质量。
