构建容器时如何制作纱线缓存模块?

stk*_*flw 14 docker yarnpkg

这是我的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" ]

Run Code Online (Sandbox Code Playgroud)

这就是我构建它的方式:

docker build --rm -f Dockerfile.dev --tag my-app .
Run Code Online (Sandbox Code Playgroud)

这就是我运行它的方式:

docker run --rm -it --volume $(pwd)/src:/usr/app/src -p 3000:3000 my-app
Run Code Online (Sandbox Code Playgroud)

仅当文件夹外部的内容发生更改时,我才需要构建它src。例如,当我安装节点模块时。如何yarn在某处缓存模块,这样就不会在每个构建上提取所有模块。

Ste*_*oob 31

使用 Docker 构建下一代容器是使用Buildkit。我建议使用它,特别是因为它对缓存问题有一个优雅的解决方案。目前在普通 Docker 中确实没有一个好的解决方案;虽然你可以解决这个问题,但它非常麻烦。

我将在这里列出这两种解决方案:

带有构建套件

塔伦的答案是正确的,但有一种更简洁的方法。Buildkit 支持将安装指定为缓存。一旦你设置了 Docker 使用 Buildkit,我们需要做的就是:

...
RUN --mount=type=cache,target=/root/.yarn YARN_CACHE_FOLDER=/root/.yarn yarn install
...
Run Code Online (Sandbox Code Playgroud)

这将自动提取上一次运行的缓存,如果尚不存在或已过期,则创建它。就是这么简单。

香草码头工人

或者,如果无法使用 Buildkit,则可以使用 vanilla Docker。我们在这里可以做的最好的事情是使用COPY指令复制位于构建上下文中的某种“缓存”。例如,如果我们.yarn_cache在构建上下文的根目录中创建一个目录,那么我们可以提供一个缓存:

...
COPY .yarn_cache /root/.yarn
RUN yarn --frozen-lockfile
...
Run Code Online (Sandbox Code Playgroud)

当你的镜像构建时,这个外部缓存不会更新,它需要在你的镜像之外进行初始化和定期更新。您可以使用以下 shell 命令来执行此操作(在第一次运行时清除任何本地命令node_modules以强制其预热缓存):

$ YARN_CACHE_FOLDER=.yarn_cache yarn install
Run Code Online (Sandbox Code Playgroud)

虽然这可行,但它非常 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" ]
Run Code Online (Sandbox Code Playgroud)

  • Vanilla Docker 不可移植——您在与容器运行的环境不同的环境中构建node_modules。节点包能够指定它们安装在哪个操作系统或体系结构上,因此这对于该解决方案来说是有问题的。 (4认同)

Eva*_*ine 6

Tarun LalwaniSteveGoob的答案很棒,但他们忽略了一个重要的细节,人们在并行构建许多容器时可能会面临这一细节。

在我的例子中,我使用命令并行地为两种架构构建了包含许多容器的 docker compose 文件buildx bake

docker buildx bake -f ./docker-compose.yml --set *.platform=linux/amd64,linux/arm64/v8 --pull --push
Run Code Online (Sandbox Code Playgroud)

如果我--mount按照建议插入参数,构建将会失败,因为 buildx 将尝试并行执行几个yarn install,这会使缓存不一致并完全破坏它。

所以我稍微改变了 RUN 命令。这是一个新版本:

RUN --mount=type=cache,target=/usr/local/share/.cache/yarn/v6,sharing=locked yarn install
Run Code Online (Sandbox Code Playgroud)

首先,我决定不创建自己的缓存目录,而是挂载到默认目录。我怎么得到默认的呢?我只是跑

RUN --mount=type=cache,target=/usr/local/share/.cache/yarn/v6,sharing=locked yarn install
Run Code Online (Sandbox Code Playgroud)

它打印了纱线缓存目录的当前路径。就我而言(也可能是大多数其他人),它将是/usr/local/share/.cache/yarn/v6。因此不需要创建任何额外的文件夹并将其作为环境变量传递。

接下来的事情是添加sharing=locked参数到--mount. 使用此参数,它将按顺序等待每个并行安装。第一个(对于第一个容器和第一个架构)将拉取所有包,将它们保存到缓存中,所有接下来的yarn install将重用该缓存。

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