Docker COPY files using glob pattern?

There is a solution based on multistage-build feature:

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

On Step 5 the layer cache will be reused even if any file in packages directory has changed.


If you can't technically enumerate all the subdirectories at stake in the Dockerfile (namely, writing COPY packages/one/package.json packages/one/ for each one), but want to copy all the files in two steps and take advantage of Docker's caching feature, you can try the following workaround:

  • Devise a wrapper script (say, in bash) that copies the required package.json files to a separate directory (say, .deps/) built with a similar hierarchy, then call docker build …
  • Adapt the Dockerfile to copy (and rename) the separate directory beforehand, and then call yarn install --pure-lockfile

All things put together, this could lead to the following files:

./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" .

and

./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...)