Docker 使用 glob 模式复制文件?

问题描述 投票:0回答:7

我有一个由 Yarn 管理的 monorepo,我想利用 Docker 缓存层来加速我的构建,为此我想首先复制

package.json
yarn.lock
文件,运行
yarn install 
然后复制其余文件。

这是我的回购结构:

packages/one/package.json
packages/one/index.js
packages/two/package.json
packages/two/index.js
package.json
yarn.lock

这是 Dockerfile 中令人感兴趣的部分:

COPY package.json .
COPY yarn.lock .
COPY packages/**/package.json ./
RUN yarn install --pure-lockfile
COPY . .

问题是第三个

COPY
命令没有复制任何内容,我怎样才能达到预期的结果?

docker dockerfile yarnpkg
7个回答
57
投票

有一个基于 multistage-build 功能的解决方案:

FROM node:12.18.2-alpine3.11

WORKDIR /app
COPY ["package.json", "yarn.lock", "./"]
# Step 2: Copy whole app
COPY packages packages

# Step 3: Find and remove non-package.json files
RUN find packages \! -name "package.json" -mindepth 2 -maxdepth 2 -print | xargs rm -rf

# Step 4: Define second build stage
FROM node:12.18.2-alpine3.11

WORKDIR /app
# Step 5: Copy files from the first build stage.
COPY --from=0 /app .

RUN yarn install --frozen-lockfile

COPY . .

# To restore workspaces symlinks
RUN yarn install --frozen-lockfile

CMD yarn start

Step 5
上,即使
packages
目录中的任何文件已更改,图层缓存也将被重用。


27
投票

官方 Dockerfile 参考中所述

COPY <src> <dest>

COPY 指令从

<src>
复制新文件或目录,并将它们添加到路径
<dest>
处的容器文件系统中。

针对您的情况

每个可能包含通配符,并且将使用 Go 的

filepath.Match
规则进行匹配。

这些是规则。它们包含这个:

'*' 匹配任何非分隔符字符序列

因此,请尝试在您的模式中使用

*
而不是
**


19
投票

如果您无法在技术上枚举 Dockerfile 中的所有子目录(即为每个子目录编写

COPY packages/one/package.json packages/one/
),但希望分两步复制所有文件并利用 Docker 的缓存功能,您可以尝试以下解决方法:

  • 设计一个包装器脚本(例如,在 bash 中),将所需的
    package.json
    文件复制到使用类似层次结构构建的单独目录(例如,
    .deps/
    ),然后调用
    docker build …
  • 调整 Dockerfile 以预先复制(并重命名)单独的目录,然后调用
    yarn install --pure-lockfile

将所有内容放在一起,可能会生成以下文件:

./build.bash:

#!/bin/bash

tag="image-name:latest"

rm -f -r .deps  # optional, to be sure that there is
# no extraneous "package.json" from a previous build

find . -type d \( -path \*/.deps \) -prune -o \
  -type f \( -name "package.json" \) \
  -exec bash -c 'dest=".deps/$1" && \
    mkdir -p -- "$(dirname "$dest")" && \
    cp -av -- "$1" "$dest"' bash '{}' \;
# instead of mkdir + cp, you may also want to use
# rsync if it is available in your environment...

sudo docker build -t "$tag" .

./Dockerfile:

FROM …

WORKDIR /usr/src/app

# COPY package.json .  # subsumed by the following command
COPY .deps .
# and not "COPY .deps .deps", to avoid doing an extra "mv"
COPY yarn.lock .
RUN yarn install --pure-lockfile

COPY . .
# Notice that "COPY . ." will also copy the ".deps" folder; this is
# maybe a minor issue, but it could be avoided by passing more explicit
# paths than just "." (or by adapting the Dockerfile and the script and
# putting them in the parent folder of the Yarn application itself...)

19
投票

使用 Docker 的新 BuildKit 执行器,可以使用绑定挂载到 Docker 上下文中,然后您可以根据需要从中复制任何文件。

例如,以下代码片段将 Docker 上下文中的所有 package.json 文件复制到映像的

/app/
目录(以下示例中的 workdir)

不幸的是,更改挂载中的任何文件仍然会导致层缓存未命中。这可以使用@mbelsky提出的多阶段方法来解决,但这次不再需要显式删除。

# syntax = docker/dockerfile:1.2 FROM ... AS packages WORKDIR /app/ RUN --mount=type=bind,target=/docker-context \ cd /docker-context/; \ find . -name "package.json" -mindepth 0 -maxdepth 4 -exec cp --parents "{}" /app/ \; FROM ... WORKDIR /app/ COPY --from=packages /app/ .
指定 

mindepth

/
maxdepth
 参数是为了减少要搜索的目录数量,可以根据您的用例进行调整/删除。

可能需要使用环境变量

DOCKER_BUILDKIT=1

启用BuildKit执行器,因为传统的执行器会默默地忽略绑定安装。

有关 BuildKit 和绑定边界的更多信息

可以在此处找到


2
投票
按照@Joost的建议,我创建了一个

dockerfile

,它利用BuildKit的力量来实现以下目标:

    更快
  • npm install
    通过将npm的缓存目录移动到构建缓存
  • 如果自上次成功构建以来
  • npm install
     文件中没有任何更改,则跳过 
    package.json
    
    
伪代码:

    从构建上下文中获取所有
  • package.json
    文件
  • 将它们与上次成功构建的
  • package.json
     文件进行比较
  • 如果发现更改,请运行
  • npm install
     并缓存 
    package.json
     文件 + 
    node_modules
     文件夹
  • node_modules
    (新鲜或缓存)复制到图像中的所需位置
# syntax = docker/dockerfile:1.2 FROM node:14-alpine AS builder # https://github.com/opencollective/opencollective/issues/1443 RUN apk add --no-cache ncurses # must run as root RUN npm config set unsafe-perm true WORKDIR /app # get a temporary copy of the package.json files from the build context RUN --mount=id=website-packages,type=bind,target=/tmp/builder \ cd /tmp/builder/ && \ mkdir /tmp/packages && \ chown 1000:1000 /tmp/packages && \ find ./ -name "package.json" -mindepth 0 -maxdepth 6 -exec cp --parents "{}" /tmp/packages/ \; # check if package.json files were changed since the last successful build RUN --mount=id=website-build-cache,type=cache,target=/tmp/builder,uid=1000 \ mkdir -p /tmp/builder/packages && \ cd /tmp/builder/packages && \ (diff -qr ./ /tmp/packages/ || (touch /tmp/builder/.rebuild && echo "Found an updated package.json")); USER node COPY --chown=node:node . /app # run `npm install` if package.json files were changed, or use the cached node_modules/ RUN --mount=id=website-build-cache,type=cache,target=/tmp/builder,uid=1000 \ echo "Creating NPM cache folders" && \ mkdir -p /tmp/builder/.npm && \ mkdir -p /tmp/builder/modules && \ echo "Copying latest package.json files to NPM cache folders" && \ /bin/cp -rf /tmp/packages/* /tmp/builder/modules && \ cd /tmp/builder/modules && \ echo "Using NPM cache folders" && \ npm config set cache /tmp/builder/.npm && \ if test -f /tmp/builder/.rebuild; then (echo "Installing NPM packages" && npm install --no-fund --no-audit --no-optional --loglevel verbose); fi && \ echo "copy cached NPM packages" && \ /bin/cp -rfT /tmp/builder/modules/node_modules /app/node_modules && \ rm -rf /tmp/builder/packages && \ mkdir -p /tmp/builder/packages && \ cd /app && \ echo "Caching package.json files" && \ find ./ -name "package.json" -mindepth 0 -maxdepth 6 -exec cp --parents "{}" /tmp/builder/packages/ \; && \ (rm /tmp/builder/.rebuild 2> /dev/null || true);
注意:
我只使用根文件夹的

node_modules

,在我的例子中,内部文件夹中的所有包都被提升到根


0
投票
对于 2024 年 3 月 6 日之后仍然对此主题感兴趣的人。

您可以将

--parents

 标志与 
COPY
 指令一起使用。从 
Dockerfile 1.7.0-labs 开始可用:

# syntax=docker/dockerfile:1.7-labs

    新的 --parents 标志已添加到 COPY 中,用于在保留父目录结构的同时复制文件。
  • moby/buildkit#4598moby/buildkit#3001moby/buildkit#4720moby/buildkit#4728docs
请注意,这在稳定的 sytnax 中尚不可用,您需要添加

# syntax=

 指令。

# syntax=docker/dockerfile:1.7-labs FROM node:20-alpine AS build WORKDIR /app COPY --parents packages/*/package.json .
❯❯❯ docker build --tag foobarimg .
❯❯❯ docker run -it foobarimg sh
/app # find .
.
./packages
./packages/foo
./packages/foo/package.json
./packages/bar
./packages/bar/package.json
    

-9
投票
只需使用

.dockerignore

 过滤掉不需要的文件。 
参考此参考资料

根据您的情况,将其添加到您的 .dockerignore 中。

*.js any file to skip copy



我假设您的文件位于类似

/home/package.json

 的位置,并且想要将这些文件复制到 docker 中的 
/dest

Dockerfile 看起来像这样。

COPY /home /dest



这会将所有文件复制到 /home 目录,除了

.dockerignore

 中的列表
    

© www.soinside.com 2019 - 2024. All rights reserved.