【问题标题】:Docker COPY files using glob pattern?Docker COPY 文件使用 glob 模式?
【发布时间】:2018-10-01 01:43:24
【问题描述】:

我有一个由 Yarn 管理的 monorepo,我想利用 Docker 缓存层来加速我的构建,为此我想先复制 package.jsonyarn.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命令没有复制任何东西,我怎样才能达到预期的效果?

【问题讨论】:

  • 基本上你想做的事情不能按原样工作,因为指定的目标文件夹对于多个文件同名(package.json )。类似地,Bash 命令cp packages/*/package.json ./ 不会产生合理的结果。所以我相信你应该在你的Dockerfile文件夹路径中硬编码onetwo...
  • 那么,您选择了哪种解决方案?
  • 我没有选择任何一个。我无法在我的环境中使用外部脚本。

标签: docker dockerfile yarnpkg


【解决方案1】:

official Dockerfile reference for COPY <src> <dest>中所述

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

根据你的情况

每个都可能包含通配符,并且将使用 Go 的 filepath.Match 规则进行匹配。

这些是the rules。它们包含以下内容:

'*' 匹配任意非分隔符序列

因此请尝试在您的模式中使用* 而不是**

【讨论】:

  • 感谢您的回复,我也尝试过,但效果相同(无)
  • 我刚刚用这个 dockerfile 试了一下,它工作正常:FROM ubuntu WORKDIR /app COPY */*.csproj /app/ 当我运行它时,这是正确的输出:$ docker run --rm -ti temp ls /app foo.csproj bar.csproj
  • 知道如何在复制文件夹结构时使其匹配吗?仅使用它就可以将其全部转到当前目录
  • @GiovanniBassi,您评论中的脚本没有按预期工作。每个 .csproj 都应该复制到适当的子文件夹(例如 app/foo/foo.csproj)而不是根 app/foo.csproj
【解决方案2】:

正如我在上面的评论中所述,使用带有 glob 的 COPY 指令来指定包含同名文件的多个子文件夹是行不通的;但一个简单的解决方案是硬编码子文件夹的路径,通过编写如下内容:

# ...
WORKDIR /usr/src/app
COPY package.json .
COPY yarn.lock .
COPY packages/one/package.json packages/one/
COPY packages/two/package.json packages/two/
RUN yarn install --pure-lockfile
COPY . .
# ...

我记得COPY 指令负责创建目标文件夹(即,no 需要 RUN mkdir -p packages/one 左右)

【讨论】:

    【解决方案3】:

    只需使用.dockerignore 过滤掉不需要的文件。 refer this reference

    在您的情况下,将其添加到您的 .dockerignore。

    *.js any file to skip copy

    我假设您的文件位于 /home/package.json 之类的位置,并且希望将这些文件复制到 docker 中的 /dest

    Dockerfile 看起来像这样。 COPY /home /dest

    这会将所有文件复制到 /home 目录中,.dockerignore 中的列表除外

    【讨论】:

    • 那我应该如何复制剩下的文件呢?
    • @FezVrasta 它将递归复制整个目录,除了.dockerignore文件中列出的目录。
    • 没错。请阅读问题。我需要复制整个目录,但分两步
    【解决方案4】:

    要跟进@FezVrasta 对我的first answer 的评论,如果您无法枚举 Dockerfile 中的所有子目录,但想分两步复制所有文件以利用 Docker 缓存功能,您可以尝试以下解决方法:

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

    所有东西放在一起,这可能会导致以下文件:

    ### ./build.bash ###
    #!/bin/bash
    
    tag=copy-example: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...)
    

    【讨论】:

      【解决方案5】:

      有一个基于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 目录中的任何文件已更改,图层缓存也将被重用。

      【讨论】:

        【解决方案6】:

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

        例如,下面的 sn -p 将所有 package.json 文件从 Docker 上下文复制到镜像的 /app/ 目录(下例中的 workdir)

        不幸的是,更改挂载中的任何文件仍会导致层缓存未命中。这可以使用多阶段方法as presented by @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 和绑定边界can be found here 的更多信息。

        【讨论】:

        • 谢谢。我已经尝试过了,值得一提的是,更改任何文件(不仅是package.json)都会导致复制步骤再次运行,所以从这个意义上说,它与复制整个代码并运行npm install相比没有任何优势跨度>
        • @Arik 哦,这很有趣!让我有点惊讶;我希望生成的图像具有相同的 SHA,因此允许重复使用后续层。我看过这项工作,但您可能是正确的,只有在没有其他任何改变的情况下。需要更多调查,看看这是否可以发挥作用!
        • @Arik 一些实验让我相信多阶段技巧对于实现所需的层缓存仍然是必要的。我已经相应地更新了示例。感谢您的观察和评论!
        • 我已添加我的解决方案作为答案
        【解决方案7】:

        根据@Joost 的建议,我创建了一个dockerfile,它利用 BuildKit 的强大功能实现以下目标:

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

        伪代码:

        • 从构建上下文中获取所有 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,就我而言,内部文件夹中的所有包都被提升到根目录

        【讨论】:

          猜你喜欢
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 2017-06-27
          • 1970-01-01
          • 2016-02-08
          • 2021-08-08
          • 2015-09-21
          相关资源
          最近更新 更多