如何在构建容器时缓存yarn模块?

13
这是我用于本地开发的Dockerfile文件:
FROM node:12-alpine

WORKDIR /usr/app

ENV __DEV__ 1

COPY package.json ./
COPY yarn.lock ./
RUN yarn --frozen-lockfile

COPY tsconfig.json ./
COPY nodemon.json ./

RUN apk add --no-cache tini
ENTRYPOINT ["/sbin/tini", "--"]

CMD [ "yarn", "dev" ]

以下是我建立它的步骤:

docker build --rm -f Dockerfile.dev --tag my-app .

我是如何运行它的:

docker run --rm -it --volume $(pwd)/src:/usr/app/src -p 3000:3000 my-app

我需要只在 src 文件夹外部的某些内容更改时才进行构建,例如安装节点模块。如何让 yarn 将模块缓存在某个地方,以便不必在每次构建时拉取所有模块。

3个回答

30
下一代使用Docker构建容器的方法是使用Buildkit。我推荐使用它,特别是因为它对于缓存问题有一个优雅的解决方案。目前在原始的Docker中并没有一个很好的解决方案;虽然你可以绕过它,但非常麻烦。
我将在这里列出两种解决方案:
使用Buildkit Tarun的答案是正确的方向,但有一种更简洁的方法。Buildkit支持将挂载指定为缓存。一旦你设置Docker使用Buildkit,我们只需要做以下操作:
...
RUN --mount=type=cache,target=/root/.yarn YARN_CACHE_FOLDER=/root/.yarn yarn install
...

这将自动拉取上一次运行的缓存,如果缓存不存在或已过期,则会创建新的缓存。就是这么简单。
香草Docker 或者,如果无法使用Buildkit,您可以使用香草Docker。在这里,我们可以使用COPY指令将构建上下文中的某种“缓存”复制进来。例如,如果我们在构建上下文的根目录中创建一个.yarn_cache目录,那么我们可以提供一个缓存:
...
COPY .yarn_cache /root/.yarn
RUN yarn --frozen-lockfile
...

这个外部缓存在构建图像时不会被更新,需要在图像之外进行初始化和定期更新。您可以使用以下shell命令来完成这个操作(在第一次运行时清除任何本地的node_modules以强制它预热缓存):
$ YARN_CACHE_FOLDER=.yarn_cache yarn install

现在虽然这个方法可以工作,但是它非常hack-y,并且有一些缺点:
- 您需要手动创建和更新缓存。 - 整个.yarn_cache目录需要包含在构建上下文中,这可能非常慢,更不用说它将在每次构建时执行此操作,即使没有任何更改。
因此,出于这些原因,前一种解决方案更受青睐。

额外专业提示:在上述任何情况下,包括纱线缓存仍会留在最终图像中,增加其大小。如果您使用多阶段构建,可以缓解此问题:

# syntax = docker/dockerfile:1.2
FROM node:12-alpine as BUILDER

WORKDIR /usr/app

COPY package.json ./
COPY yarn.lock ./
RUN --mount=type=cache,target=/root/.yarn YARN_CACHE_FOLDER=/root/.yarn yarn --frozen-lockfile


FROM node:12-alpine

WORKDIR /usr/app

COPY --from=BUILDER node_modules ./node_modules


COPY package.json ./
COPY yarn.lock ./
COPY tsconfig.json ./
COPY nodemon.json ./

RUN apk add --no-cache tini
ENTRYPOINT [ "/sbin/tini", "--" ]

ENV __DEV__=1

CMD [ "yarn", "dev" ]

4
传统的Docker不具备可移植性--因为你在构建node_modules时使用的环境可能与容器运行的环境不同。Node包能够指定其安装的操作系统或架构,所以这种解决方案存在问题。 - AndrewKS
yarn cache dir 可以在主机上使用,以查看 yarn 缓存存储的具体位置。 - undefined

6

Tarun LalwaniSteveGoob的答案很好,但他们忽略了一个重要细节,即当人们并行构建多个容器时可能会面临的问题。

在我的情况下,我使用buildx bake命令并行构建了两个架构的许多容器的Docker Compose文件:

docker buildx bake -f ./docker-compose.yml --set *.platform=linux/amd64,linux/arm64/v8 --pull --push

如果我按建议插入--mount参数,构建将失败,因为buildx会尝试并行执行几个yarn install,这会使缓存不一致并完全破坏它。
所以我稍微更改了RUN命令。这里是一个新版本:
RUN --mount=type=cache,target=/usr/local/share/.cache/yarn/v6,sharing=locked yarn install

首先,我决定不创建自己的缓存目录,而是挂载到默认目录。我如何获取默认目录?我只需运行

docker run -it node:18-alpine yarn cache dir

它打印出了yarn缓存目录的当前路径。在我的情况下(可能也是大多数情况),它将位于/usr/local/share/.cache/yarn/v6。因此,没有必要创建任何额外的文件夹并将其作为环境变量传递。

接下来要做的是在--mount中添加sharing=locked参数。使用这个参数,它会按顺序等待每个并行安装。第一个(用于第一个容器和第一个架构)将拉取所有软件包,将它们保存到缓存中,而所有后续的yarn install都将重用该缓存。

如果你不喜欢它们互相等待,你可以使用sharing=private以一些冗余创建每个容器+架构对应的独立缓存。来自文档的原始信息


3
您可以使用 BuildKit 来完成相同的操作。

https://docs.docker.com/develop/develop-images/build_enhancements/

--mount=type=cache in buildkit

Yarn可以在构建过程中缓存下载的软件包。请查看所有可用选项。

https://classic.yarnpkg.com/en/docs/cli/cache/

YARN_CACHE_FOLDER=<path> yarn <command>

所以您将在您的Dockerfile中使用以下内容:
RUN --mount=type=bind,source=./.yarn,target=/root/.yarn,rw YARN_CACHE_FOLDER=/root/.yarn yarn install

您可以在 Dockerfile 中早期使用 ENV,这样您就不需要一遍又一遍地重复使用 YARN_CACHE_FOLDER

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接