111

Is there any way to copy multiple directories in one command, to reduce the number of layers? E.g., instead of:

COPY dirone ./dirone
COPY dirtwo ./dirtwo
COPY dirthree ./dirthree

I want to do:

COPY dirone/ dirtwo/ dirthree/ ./

However, this copies the contents of the directories... but I want to copy the directories themselves.

Claudiu
  • 224,032
  • 165
  • 485
  • 680

5 Answers5

64

That's the documented behavior of the copy command:

If <src> is a directory, the entire contents of the directory are copied, including filesystem metadata.

Note: The directory itself is not copied, just its contents.

Best workaround I can suggest is to change your directory layout in your build folder, move the three folders under one parent folder and add the parent.

BMitch
  • 231,797
  • 42
  • 475
  • 450
  • 83
    I've been tearing my hair out for hours wondering why certain files were missing from my container. Turns out this is the reason. Why, oh why, can't COPY just work like cp?? – colincameron Oct 03 '18 at 14:21
  • Using `docker cp` you can end the path with `/.` to copy only the contents. Unsure if that works here though. – Ben Nov 17 '21 at 15:39
  • 1
    @Ben The topic is about `Dockerfile` `COPY` command not `docker cp`. – haridsv Aug 05 '22 at 14:37
22
  1. You can copy entire parent directory and exclude all other folders/files in .dockerignore file

Dockerfile

COPY . ./

.dockerignore

/dirfour
/dirfive
/file.txt
  1. Or you can ignore entire parent folder in .dockerignore and include only folders you want to copy

Dockerfile

COPY . ./

.dockerignore

/**
!/dirone
!/dirtwo
!/dirthree
fela
  • 679
  • 8
  • 7
  • 1
    Wondering why is this not the accepted or highly voted answer? – SunnyPro May 05 '22 at 21:39
  • @SunnyPro This will disable build caching in most cases. If you build the same Dockerfile two times, the second run will likely be much faster. With `COPY .`, it'll only be that fast if you don't change any file in your `.` folder between builds – merlindru Jun 08 '22 at 21:43
  • Long-winded explanation: By default, docker uses build caching. If an instruction like `COPY` would have _exactly the same result_, it's skipped and uses the previous build's result instead. If you edit just one file in the `` directory you're copying (in this case `.`) the build cache will be invalidated, making Docker copy all the files again. If you used `COPY dirone` instead, it would only have to copy all of `dirone` again if a file in `dirone` changed. This is not an issue with very small `COPY`s, but copying large folders will be slow! – merlindru Jun 08 '22 at 21:50
  • It is a common misconception that `COPY` is slow. Actually the `docker build` command itself is slow, because that's where the *context* (essentially the entire folder tree, except ignored parts) is being sent over to Docker. – rustyx Dec 18 '22 at 14:44
20

As BMitch answered, that is expected COPY behaviour.

An alternative would be to ADD the contents of a tarball.

Create the initial tarball

tar -cvf dirs.tar dirone/ dirtwo/ dirthree/

Add it to the build

FROM busybox
ADD dirs.tar /
CMD find /dirone /dirtwo /dirthree

The tarball is automatically extracted

○ →docker run c28f96eadd58
/dirone
/dirone/one
/dirtwo
/dirtwo/two
/dirthree
/dirthree/three

Note that every time you update the tar file you are invalidating the Docker build cache for that step. If you are dealing with a lot of files you might want to be smart about when you do the tar -c. You could also use tar -u if you can deal with files not being automatically deleted from the tarball.

[ -f dirs.tar ] && tar -uf dirs.tar something || tar -cf dirs.tar something
Matt
  • 68,711
  • 7
  • 155
  • 158
  • One question please, I assume that if necessary, one could make create several tar in order to optimize its application? for example, tarOne for some file, tarTwo with other files, allowing to reduce the size of each tar and the occurence of the rebuilding – Webwoman May 31 '19 at 02:59
  • @Webwoman yes, that would work. The general rule is to put the least changey things first in a dockerfile. The same could be done with the tar files if you have a large set of files that don't change, separate them out – Matt Apr 22 '20 at 20:44
-1

Along the lines of the previous answers, but with the (relatively modern) multiple FROM support:

FROM alpine AS src
RUN mkdir -p /src /dst/a /dst/b /dst/rest
WORKDIR /src
COPY . .
RUN true \
    && mv a aa aaa /dst/a/ \
    && mv b bb bbb /dst/b/ \
    && mv * /dst/rest/


FROM realbaseimage

COPY --stage=src /dst/a .
RUN do stuff that needs only a

COPY --stage=src /dst/b .
RUN do stuff that needs only b

COPY --stage=src /dst/rest .
RUN do stuff that needs the rest

This will layer and cache properly: the layers created in the src stage won't be pushed, so the copy/run layers in the final image will be sized and cached according to the contents of parts rather than having duplication and cache invalidation of the whole when changing one thing.

You can change the src stage's base image to whatever, but it needs to have the mv binary, obviously.

Félix Saparelli
  • 8,424
  • 6
  • 52
  • 67
-4

The actual solution, that will not change your code and will use only dockerfile

COPY . /tmp/
WORKDIR /tmp/
RUN cp -r dirone/ dirtwo/ dirthree/ /full_path_to_app/
WORKDIR /full_path_to_app/

Be aware, that:

  • You need to change your workdir back for something useful, after /tmp.
  • You can do it without workdir, but then you will need to repeat path RUN cp -r /tmp/dirone/ /tmp/dirtwo/ /tmp/dirthree/ ./
  • Destination path must be absolute, otherwise, it will be related to workdir
  • Destination should end with /
bm13kk
  • 167
  • 9
  • 2
    This still uses two layers, and both of them will have a full copy of your file, meaning the image will be bigger. – remram Sep 08 '21 at 17:37