Docker chapter 6 镜像构建优化 - 依赖缓存 与 多阶段构建

old dockerfile

cpp 复制代码
# syntax=docker/dockerfile:1

FROM node:18-alpine
WORKDIR /app
RUN yarn install --production
COPY . .
CMD ["node", "src/index.js"]
EXPOSE 3000

# syntax=docker/dockerfile:1 是 Dockerfile 的一个解析器指令,它用于声明构建时使用的 Dockerfile 语法版本。

如果你不指定这个指令,BuildKit 将使用它内置的 Dockerfile 前端版本。声明一个语法版本可以让你自动使用最新的 Dockerfile 版本,而无需升级 BuildKit 或 Docker Engine,甚至可以使用自定义的 Dockerfile 实现。

大多数用户会将这个解析器指令设置为 docker/dockerfile:1,这会让 BuildKit 在构建前拉取 Dockerfile 语法的最新稳定版本。

在你的 Dockerfile 中,# syntax=docker/dockerfile:1 指令告诉 Docker 使用最新稳定版本的 Dockerfile 语法来解析和构建这个 Dockerfile。

note 1 :

EXPOSE 3000 和 docker run -dp 127.0.0.1:3001:3000 getting-started-2 的关联

Dockerfile 中的 EXPOSE 指令只是声明了应用程序在容器内部监听的端口,它并不会自动映射这个端口到宿主机。如果你想让应用程序可以从宿主机访问,你需要在启动容器时使用 -p--publish 参数来映射端口。

容器启动时映射的端口并不一定要和 EXPOSE 指令声明的端口相同。例如,你的 Dockerfile 中有 EXPOSE 3000,但你可以在启动容器时使用 -p xxxx:3000 来将容器的 3000 端口映射到宿主机的 xxxx 端口。

但是,如果你的应用程序在容器内部监听的端口和 EXPOSE 指令声明的端口不同,那么 EXPOSE 指令就没有意义了。因为 EXPOSE 指令的主要目的就是告诉使用这个镜像的人,应用程序在哪个端口提供服务。镜像就是一个模版,可以同时生成多个容器实例服务

这样就相当于你可以将启动多个主机端口 映射同样的实例服务

3001 -> 3000 3002 -> 3000

note 2 : caching of the dependencies

watching the old dockerFile command line 。 at some circumstance , we will recreated image when update code ,but it will download dependencies again 。 that's terrible , please following to optimize the dockerFile 。

cpp 复制代码
# syntax=docker/dockerfile:1

FROM node:18-alpine
WORKDIR /app
COPY package.json yarn.lock ./
RUN yarn install --production
COPY . .
CMD ["node", "server/test.js"]
EXPOSE 3000

原因 :

Once a layer changes, all downstream layers have to be recreated as well

一旦一个图层改变,所有的下游层都会重新构建

Docker 使用一种称为层(layer)的概念来构建和存储镜像。每个 Dockerfile 指令都会创建一个新的层,并在此基础上进行下一步操作。这些层是只读的,但可以被后续的层修改。

Docker 会尽可能地使用缓存来加速镜像构建过程。当 Docker 构建镜像时,它会查看每个指令和之前构建的镜像层。如果 Docker 发现一个指令和一个已经缓存的层完全匹配,那么它就会使用这个缓存的层,而不是重新执行指令。

例如,如果你的 Dockerfile 的前两个指令是 FROM node:18-alpineWORKDIR /app,并且你之前已经构建过一个使用相同指令的镜像,那么 Docker 就会使用这两个缓存的层。

但是,如果一个指令的上下文(例如,被复制的文件)发生了改变,那么 Docker 就不能使用缓存,而必须重新执行指令。这就是为什么在 Dockerfile 中,我们通常先复制 package.jsonyarn.lock,然后运行 yarn install,最后再复制其余的文件。这样,即使源代码发生改变,只要依赖没有改变,yarn install 的步骤就可以使用缓存,从而加速构建过程。

如果指令不变,那么就会用缓存的老的 layer

修改 /app 为 /gyk 后 ,/app 后的所有layer 全部失效。

所以:

重新 build

note3 .dockerignore

.dockerignore 文件用于指定哪些文件或目录应该被 Docker 忽略,不应该被复制到镜像中。这与 .gitignore 文件的作用类似,但是用于 Docker。

在你提供的例子中,.dockerignore 文件的内容是:

node_modules

这意味着 node_modules 目录不会被复制到 Docker 镜像中。这是因为 node_modules 目录通常包含大量的文件,而且这些文件在构建过程中会被 yarn installnpm install 命令重新创建。因此,将 node_modules 目录复制到镜像中不仅会浪费空间,还可能导致问题,因为它可能会覆盖由 RUN 步骤创建的文件。

note 4 muti-stage builds 多阶段构建

多阶段构建(Multi-stage builds)是 Docker 提供的一种强大的工具,它允许你在一个 Dockerfile 中定义多个阶段来构建镜像。这有几个优点:

  1. 分离构建时依赖和运行时依赖:在构建阶段,你可能需要一些额外的工具和库来编译你的应用程序(例如,编译器,构建工具等)。但是在运行阶段,这些工具和库可能就不再需要了。通过使用多阶段构建,你可以在一个阶段安装和使用这些工具,然后在另一个阶段只复制你的应用程序和它需要的运行时依赖。

  2. 减少总体镜像大小:由于你只复制了应用程序和它的运行时依赖,所以最终的镜像会比包含所有构建工具和库的镜像小得多。这可以减少存储和网络传输的开销,使你的应用程序更快地启动,并且减少了安全风险,因为镜像中包含的组件更少。

下面是一个简单的多阶段构建的例子:

cpp 复制代码
# 第一阶段:构建应用程序
FROM node:18-alpine AS build
WORKDIR /app
COPY package.json yarn.lock ./
RUN yarn install
COPY . .
RUN yarn build

# 第二阶段:运行应用程序
FROM node:18-alpine
WORKDIR /app
COPY --from=build /app/dist ./dist
COPY package.json yarn.lock ./
RUN yarn install --production
CMD ["node", "dist/index.js"]

在这个例子中,第一阶段使用 node:18-alpine 镜像来安装所有依赖并构建应用程序。然后,第二阶段使用同样的 node:18-alpine 镜像,但只复制了构建的应用程序和运行时依赖,并安装了这些运行时依赖。这样,最终的镜像就只包含了运行应用程序所需要的东西。

在 Dockerfile 中,WORKDIR /app 指令设置了工作目录为 /app。这意味着后续的指令(如 COPYRUN 等)都会在这个目录下执行。

在你提供的 Dockerfile 的第二阶段中,COPY --from=build /app/dist ./dist 这行指令的作用是从构建阶段(被命名为 build)的镜像中复制 /app/dist 目录到当前工作目录下的 dist 目录。

这里的 ./dist 是相对于当前的工作目录 /app 的,所以 ./dist 实际上就是 /app/dist

所以,这行指令的效果是将构建阶段生成的应用程序(位于 /app/dist)复制到最终镜像的 /app/dist 目录下。

然后,CMD ["node", "dist/index.js"] 这行指令在容器启动时会运行 /app/dist/index.js,也就是你刚刚复制过来的应用程序

React example

When building React applications, you need a Node environment to compile the JS code (typically JSX), SASS stylesheets, and more into static HTML, JS, and CSS. If you aren't doing server-side rendering, you don't even need a Node environment for your production build. You can ship the static resources in a static nginx container.

cpp 复制代码
# syntax=docker/dockerfile:1
FROM node:18 AS build
WORKDIR /app
COPY package* yarn.lock ./
RUN yarn install
COPY public ./public
COPY src ./src
RUN yarn run build

FROM nginx:alpine
COPY --from=build /app/build /usr/share/nginx/html

example:

cpp 复制代码
# syntax=docker/dockerfile:1

FROM node:18-alpine AS build
WORKDIR /gyk
COPY package.json yarn.lock ./ 
# 依赖文件不变,不会重新下载依赖,一旦变化,后面layer失效,重新下载依赖
RUN yarn install --production
COPY . .
# 如只改动源代码,则之后执行 COPY .. 后的 指令
CMD ["node", "server/test.js"]
EXPOSE 3044


FROM nginx:alpine
COPY --from=build /gyk/. /usr/share/nginx/html
#/gyk/* 表示 gyk 目录下的所有文件和子目录,如果你想使用 . 来表示 gyk 目录下的所有内容,你可以直接写成 /gyk/.。这样,Docker 就会复制 gyk 目录下的所有文件和子目录,在 Dockerfile 中,COPY 指令不支持 ** 这种形式的通配符。** 通常在 shell 脚本或某些编程语言中用于匹配任意多级的子目录,但在 Dockerfile 的 COPY 指令中,这种语法是不被支持的

优化后 43.37 M

在 Docker 的多阶段构建中,每个阶段都必须以 FROM 指令开始,这是 Dockerfile 语法的要求。FROM 指令用于指定基础镜像,如果你不指定基础镜像,Docker 就不知道如何创建新的镜像。

如果你的第二阶段不需要任何特定的镜像,你可以使用一个非常小的基础镜像,如 scratchscratch 是一个特殊的 Docker 镜像,它是空的,不包含任何文件。这通常用于创建非常小的镜像,只包含你的应用程序和它的运行时依赖。

note 5 :node:18-alpine 和 nginx:alpine 的 alpine 是什么意思

alpine 是一个轻量级的 Linux 发行版,它的设计目标是尽可能地减小体积。alpine 镜像通常只有 5MB 左右,相比其他的 Linux 发行版,如 Ubuntu 或 Debian,它的体积小很多。

在 Docker 中,alpine 通常用作基础镜像,用于构建更小的 Docker 镜像。例如,node:18-alpine 是一个包含 Node.js 18 版本和 Alpine Linux 的 Docker 镜像,nginx:alpine 是一个包含 Nginx 和 Alpine Linux 的 Docker 镜像。

使用 alpine 作为基础镜像的好处是可以减小最终 Docker 镜像的体积,这可以减少存储和网络传输的开销,使你的应用程序更快地启动,并且减少了安全风险,因为镜像中包含的组件更少

相关推荐
HoweWWW12 分钟前
k8s-API 访问控制
云原生·容器·kubernetes
2420301 小时前
Docker笔记-容器数据卷
docker
shelby_loo2 小时前
通过 Docker 部署 WordPress 服务器
服务器·docker·容器
minsin3 小时前
【linux】【docker】Docker默认网段配置导致无法访问
docker
悲伤的创可贴5 小时前
Docker安装以及简单使用
linux·docker·centos
方圆师兄5 小时前
docker快速搭建kafka
docker·容器·kafka
小的~~6 小时前
k8s使用本地docker私服启动自制的flink集群
docker·flink·kubernetes
诚诚k7 小时前
docker存储
运维·docker·容器
sorel_ferris7 小时前
Ubuntu-24.04中Docker-Desktop无法启动
linux·ubuntu·docker
数据智能老司机7 小时前
Kubernetes从入门到精通系列——外部 DNS 和全局负载均衡
云原生·容器·kubernetes