使用构建缓存
说明
请考虑您为 入门指南 应用创建的以下 Dockerfile。
FROM node:20-alpine
WORKDIR /app
COPY . .
RUN yarn install --production
CMD ["node", "./src/index.js"]当您运行 docker build 命令创建新镜像时,Docker 会按照 Dockerfile 中的指令顺序执行每一条指令,并为每个命令创建一个层。对于每条指令,Docker 会检查是否可以复用之前构建中的指令。如果发现您之前已经执行过类似的指令,Docker 就无需重复执行,而是使用缓存结果。这样,您的构建过程会变得更加高效,为您节省宝贵的时间和资源。
有效地使用构建缓存可以通过重用先前构建的结果并跳过不必要的工作来实现更快的构建。 为了最大限度地利用缓存并避免资源密集且耗时的重新构建,了解缓存失效的工作原理非常重要。 以下是一些可能导致缓存失效的情况示例:
对
RUN指令命令的任何更改都会使该层失效。如果在您的 Dockerfile 中对RUN命令进行了任何修改,Docker 会检测到该更改并使构建缓存失效。使用
COPY或ADD指令复制到镜像中的文件有任何更改。Docker 会密切关注项目目录中文件的任何更改。无论是内容更改还是权限等属性的更改,Docker 都会将这些修改视为触发缓存失效的条件。一旦某一层失效,所有后续层也会失效。如果之前的任何一层(包括基础镜像或中间层)因更改而失效,Docker 会确保依赖它的后续层也会失效。这保持了构建过程的同步,并防止不一致性。
在编写或编辑 Dockerfile 时,请注意避免不必要的缓存未命中,以确保构建运行尽可能快速高效。
试用
在本实践指南中,您将学习如何有效地为Node.js应用程序使用Docker构建缓存。
构建应用程序
下载并安装 Docker Desktop。
打开终端并 克隆此示例应用程序。
$ git clone https://github.com/dockersamples/todo-list-app进入
todo-list-app目录:$ cd todo-list-app在此目录中,您将找到一个名为
Dockerfile的文件,其内容如下:FROM node:20-alpine WORKDIR /app COPY . . RUN yarn install --production EXPOSE 3000 CMD ["node", "./src/index.js"]执行以下命令以构建 Docker 镜像:
$ docker build .这是构建过程的结果:
[+] Building 20.0s (10/10) FINISHED第一行表示整个构建过程耗时 20.0 秒。首次构建可能需要一些时间,因为它需要安装依赖项。
在不做更改的情况下重新构建。
现在,在不更改源代码或 Dockerfile 的情况下,重新运行
docker build命令,如下所示:$ docker build .由于缓存机制,只要命令和上下文保持不变,初始构建之后的后续构建会更快。Docker 会缓存构建过程中生成的中间层。当你在不更改 Dockerfile 或源代码的情况下重新构建镜像时,Docker 可以重用缓存的层,从而显著加快构建过程。
[+] Building 1.0s (9/9) FINISHED docker:desktop-linux => [internal] load build definition from Dockerfile 0.0s => => transferring dockerfile: 187B 0.0s ... => [internal] load build context 0.0s => => transferring context: 8.16kB 0.0s => CACHED [2/4] WORKDIR /app 0.0s => CACHED [3/4] COPY . . 0.0s => CACHED [4/4] RUN yarn install --production 0.0s => exporting to image 0.0s => => exporting layers 0.0s => => exporting manifest通过利用缓存层,后续构建仅耗时 1.0 秒。无需重复安装依赖等耗时步骤。
步骤 描述 首次运行耗时 耗时(第二次运行) 1 从 Dockerfile 加载构建定义 0.0 秒 0.0 秒 2 加载 docker.io/library/node:20-alpine 的元数据 2.7秒 0.9 秒 3 加载 .dockerignore 0.0 秒 0.0 秒 4 加载构建上下文 (上下文大小: 4.60MB)
0.1 秒 0.0 秒 5 设置工作目录 (WORKDIR) 0.1 秒 0.0 秒 6 将本地代码复制到容器中 0.0 秒 0.0 秒 7 运行 yarn install --production 10.0 秒 0.0 秒 8 导出层 2.2秒 0.0 秒 9 导出最终镜像 3.0秒 0.0 秒 回到
docker image history输出,你可以看到 Dockerfile 中的每个命令都会成为镜像中的一个新层。你可能还记得,当你修改镜像时,yarn依赖项需要重新安装。有没有办法解决这个问题?每次构建时都重新安装相同的依赖项似乎没有太大意义,对吧?要解决此问题,请重新构建您的 Dockerfile,以便依赖缓存保持有效,除非确实需要使其失效。对于基于 Node 的应用程序,依赖项在
package.json文件中定义。如果该文件发生变化,您需要重新安装依赖项,但如果文件未更改,则使用缓存的依赖项。因此,首先仅复制该文件,然后安装依赖项,最后复制其他所有内容。这样,只有在package.json文件发生更改时,您才需要重新创建 yarn 依赖项。更新 Dockerfile,首先复制
package.json文件,安装依赖项,然后复制其他所有内容。FROM node:20-alpine WORKDIR /app COPY package.json yarn.lock ./ RUN yarn install --production COPY . . EXPOSE 3000 CMD ["node", "src/index.js"]在与 Dockerfile 相同的文件夹中创建一个名为
.dockerignore的文件,内容如下。node_modules构建新镜像:
$ docker build .您将看到类似于以下的输出:
[+] Building 16.1s (10/10) FINISHED => [internal] load build definition from Dockerfile 0.0s => => transferring dockerfile: 175B 0.0s => [internal] load .dockerignore 0.0s => => transferring context: 2B 0.0s => [internal] load metadata for docker.io/library/node:21-alpine 0.0s => [internal] load build context 0.8s => => transferring context: 53.37MB 0.8s => [1/5] FROM docker.io/library/node:21-alpine 0.0s => CACHED [2/5] WORKDIR /app 0.0s => [3/5] COPY package.json yarn.lock ./ 0.2s => [4/5] RUN yarn install --production 14.0s => [5/5] COPY . . 0.5s => exporting to image 0.6s => => exporting layers 0.6s => => writing image sha256:d6f819013566c54c50124ed94d5e66c452325327217f4f04399b45f94e37d25 0.0s => => naming to docker.io/library/node-app:2.0 0.0s你会看到所有层都重新构建了。这完全没问题,因为你对 Dockerfile 做了相当大的改动。
现在,对
src/static/index.html文件进行更改(例如,将标题更改为“超棒的待办事项应用”)。构建 Docker 镜像。这一次,您的输出看起来应该会有些不同。
$ docker build -t node-app:3.0 .您将看到类似于以下的输出:
[+] Building 1.2s (10/10) FINISHED => [internal] load build definition from Dockerfile 0.0s => => transferring dockerfile: 37B 0.0s => [internal] load .dockerignore 0.0s => => transferring context: 2B 0.0s => [internal] load metadata for docker.io/library/node:21-alpine 0.0s => [internal] load build context 0.2s => => transferring context: 450.43kB 0.2s => [1/5] FROM docker.io/library/node:21-alpine 0.0s => CACHED [2/5] WORKDIR /app 0.0s => CACHED [3/5] COPY package.json yarn.lock ./ 0.0s => CACHED [4/5] RUN yarn install --production 0.0s => [5/5] COPY . . 0.5s => exporting to image 0.3s => => exporting layers 0.3s => => writing image sha256:91790c87bcb096a83c2bd4eb512bc8b134c757cda0bdee4038187f98148e2eda 0.0s => => naming to docker.io/library/node-app:3.0 0.0s首先,你应该注意到构建速度快了很多。你会看到几个步骤使用了之前缓存的层。这是个好消息;你正在使用构建缓存。推送和拉取这个镜像及其更新也会快得多。
通过遵循这些优化技巧,您可以加快Docker构建速度并提高效率,从而加快迭代周期并提升开发生产力。
其他资源
后续步骤
既然您已经了解了如何有效地使用 Docker 构建缓存,那么您就可以开始学习多阶段构建了。