Copy some files and a whole folder from host into container using a unique COPY statement in a Dockerfile?

Viewed 190

I'm running a Dockerfile with two COPY statements as such:

WORKDIR /app
COPY requirements.txt entrypoint.sh ./
COPY binaries/ ./binaries

It works fine, I end up having what I need:

root:/# ls /app
requirements.txt
entrypoint.sh
binaries/
├── executable1
├── executable2
└── executable3

But I'd like to keep the number of layers as low as possible, hence I try to combine the two COPY statements into one:

WORKDIR /app
COPY requirements.txt entrypoint.sh binaries/ ./

But this is 'extracting' all the files from the binaries/ folder and lay them next to the 2 others:

root:/# ls /app
requirements.txt
entrypoint.sh
executable1
executable2
executable3

Question

How do you combine both some files and one (or more) specific folder(s) into one unique COPY statement in a Dockerfile (if it's possible of course)?

Environment information:

Docker version 20.10.0, build 7287ab3    
Host: ubuntu 18.04.5

Testing MWE

From localhost:~$

mkdir -p doc && cd doc && mkdir -p binaries && touch entrypoint.sh \
requirements.txt binaries/ex1 binaries/ex2 binaries/ex3 \
&& echo 'FROM debian:buster-slim
WORKDIR /app
COPY requirements.txt entrypoint.sh binaries ./' >> Dockerfile

From localhost:~$

docker build --no-cache -t test:latest . \
&& docker run -it --rm --name test test:latest bash 

From inside container:

root:/app# ls

gives me:

entrypoint.sh  ex1  ex2  ex3  requirements.txt

The same behaviour is encountered when using ADD instead of COPY.

2 Answers

You can create a folder with the following structure alongside your Dockerfile, and then copy the contents into your apps directory,

$ tree
.
├── Dockerfile
└── folder
    ├── binaries
    │   ├── executable1
    │   ├── executable2
    │   └── executable3
    ├── entrypoint.sh
    └── requirements.txt

2 directories, 6 files

Dockerfile,

FROM debian:buster-slim
WORKDIR /app
COPY folder .

root@b111e73a17c1:/app# tree
.
|-- binaries
|   |-- executable1
|   |-- executable2
|   `-- executable3
|-- entrypoint.sh
`-- requirements.txt

1 directory, 5 files

You won't be able to COPY or ADD a directory without specifying a destination directory, as pointed by the documentation:

  • 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.

Source: https://docs.docker.com/engine/reference/builder/#add & https://docs.docker.com/engine/reference/builder/#copy

But there are two possibilities here, since docker won't go recursively in your folder you could change your files structure on the host and do something like:

.
└── to_mount
    ├── binaries
    │   ├── executable1
    │   ├── executable2
    │   └── executable3
    ├── entrypoint.sh
    └── requirements.txt

And then simply:

COPY to_mount .

Another way that you could use, which is always nice to know of but which can prove highly unpractical based on your use case is using a .dockerignore file.

Before the docker CLI sends the context to the docker daemon, it looks for a file named .dockerignore in the root directory of the context. If this file exists, the CLI modifies the context to exclude files and directories that match patterns in it. This helps to avoid unnecessarily sending large or sensitive files and directories to the daemon and potentially adding them to images using ADD or COPY.

Source: https://docs.docker.com/engine/reference/builder/#dockerignore-file

With this you could COPY . and then add in the .dockerignore the other files that are in your current host folder but that you don't want to copy in the image.

Here is my docker folder

.
├── .dockerignore
├── Dockerfile
├── binaries
│   ├── executable1
│   ├── executable2
│   └── executable3
├── docker-compose.yml
├── entrypoint.sh
└── requirements.txt

.dockerignore contains

Dockerfile
docker-compose.yml
.dockerignore

So, doing:

COPY . .

I end up with

.
├── binaries
│   ├── executable1
│   ├── executable2
│   └── executable3
├── entrypoint.sh
└── requirements.txt

With the Dockerfile

FROM alpine

RUN apk add --no-cache tree
WORKDIR /tmp
COPY . .

We get

$ tree -a
.
├── .dockerignore
├── Dockerfile
├── binaries
│   ├── executable1
│   ├── executable2
│   └── executable3
├── docker-compose.yml
├── entrypoint.sh
└── requirements.txt

1 directory, 8 files

$ docker run --rm -ti $(docker build -q .) tree -a
.
├── binaries
│   ├── executable1
│   ├── executable2
│   └── executable3
├── entrypoint.sh
└── requirements.txt

1 directory, 5 files
Related