Dockerfile的ADD和COPY

发布时间:2024年01月14日

环境

  • RHEL 9.3
  • Docker Community 24.0.7

ADD

ADD 指令把 <src> 的文件、目录、或URL链接的文件复制到 <dest>

ADD 有两种写法:

  • ADD [--chown=<user>:<group>] [--chmod=<perms>] [--checksum=<checksum>] <src>... <dest>

例如:

ADD a.txt /mydir/
  • ADD [--chown=<user>:<group>] [--chmod=<perms>] ["<src>",... "<dest>"]

例如:

ADD ["a b.txt", "/mydir/"]

如果路径里包含空格,则必须用第二种写法。

一条ADD 指令里可以有多个 <src>

ADD a.txt b.txt /mydir/

<src> 可以使用通配符:

  • * :匹配任意0个、1个、或多个字符

例如:

ADD hom* /mydir/
  • ? :匹配任意1个字符

例如:

ADD hom?.txt /mydir/

<dest> 可以是绝对路径或相对路径。相对路径是建立在 <WORKDIR> 基础上的。

例如:

WORKDIR /dir1
ADD a.txt dir2/

则目标路径是 /dir1/dir2/

<src> 里包含特殊字符(比如 [] )时,需要按照Golang的规则转义,以避免被当作匹配模式。

比如,源文件名为 arr[0].txt ,则写法如下:

ADD arr[[]0].txt /mydir/

注:这一段话有点费解,参见我另一篇文档( https://blog.csdn.net/duke_ding2/article/details/135519942 )。

新创建的文件和目录,其UID和GID默认值都是0。可以通过 --chown 指定username/groupname(字符串)或者UID/GID(整数)。如果指定的是username/groupname字符串,则使用 /etc/passwd/etc/group 来转换为对应的UID和GID。若只指定username或只指定UID,则GID与UID相同。

例如:

ADD --chown=55:mygroup files* /somedir/
ADD --chown=bin files* /somedir/
ADD --chown=1 files* /somedir/
ADD --chown=10:11 files* /somedir/
ADD --chown=myuser:mygroup --chmod=655 files* /somedir/
  • 如果指定的username或groupname在 /etc/passwd/etc/group 里查找不到,则使用默认值0。

例如:

ADD --chown=xxx:yyy a.txt dir2/

结果为:

docker run kai0111_2 ls -l dir2
total 4
-rw-r--r--    1 root     root            12 Jan 11 07:20 a.txt
  • 如果指定的是整数,则不查看 /etc/passwd/etc/group ,指定的什么值就是什么值。

例如:

ADD --chown=111:222 a.txt dir2/

结果为:

docker run kai0111_2 ls -l dir2
total 4
-rw-r--r--    1 111      222             12 Jan 11 07:20 a.txt
  • 如果文件系统不包含 /etc/passwd/etc/group 文件,而在 ADD 指令里指定了username/groupname字符串,则构建会失败。

  • 如果 <src> 是远程的文件或URL,则目标文件的权限是“600”。

例如:

ADD https://github.com/dockersamples/buildme/blob/main/README.md dir1/

构建后,结果为:

docker run kai0111_3 ls -l dir1
total 136
-rw-------    1 root     root        137739 Jan  1  1970 README.md
  • 在获取远程文件时,如果response里有HTTP last-modified header,则该值会被用来指定目标文件的mtime。

在上一个例子中,response里没有HTTP last-modified header,所以mtime被指定为1970年1月1日。

下面是一个有HTTP last-modified header的例子:

在这里插入图片描述

添加该URL:

ADD https://docs.docker.com/guides/get-started/ dir1/

构建后,结果为:

docker run kai0111_4 ls -l dir1
total 68
-rw-------    1 root     root         68424 Jan 10 15:29 get-started

可见,修改时间时间和response里 last-modified header的值是一致的。

注意:mtime不会用来判断文件是否有修改以及是否要更新缓存。

  • 如果URL的文件受验证保护,则需要使用 RUN wgetRUN curl 或者其它方法,因为 ADD 指令不支持身份验证。

  • 如果 <src> 的内容有变化,则 ADD 指令的cache失效,随后的指令的cache也都失效,包括 RUN 指令。显然,应该合理放置 ADD 指令,使其只影响与源文件变化相关的步骤。

规则

ADD 指令有如下规则:

  • <src> 必须在build context里。不能使用 ../xxx ,也不能使用绝对路径。
  • 如果 <src> 是目录,则目录的所有内容都会复制,包括文件系统元数据。(注意:目录本身并不复制,只复制目录里面的内容。)
  • 如果 <src> 是URL,且 <dest>/ 结尾,则文件名由URL推断,下载到 <dest>/<filename> 。例如, ADD http://example.com/foobar / 会复制文件到 /foobar 。URL必须能推断出文件名,像 http://example.com 这样的URL是不行的。
  • 如果 <src> 是本地的压缩的tar包(比如 identitygzipbzip2xz ),则会被解压为一个目录。但如果 <src> 是远程文件,则不会解压。当目录被复制或解压时,其行为和 tar -x 是相同的。结果是以下的结合:
    1. Whatever existed at the destination path and
    2. The contents of the source tree, with conflicts resolved in favor of “2.” on a file-by-file basis.

注:这段话我没看懂是什么意思……我猜测意思是:目标路径下可能已经存在一些文件和目录,现在把tar包解压到目标路径时,可能会产生冲突,会按照“favor of “2.””逐个文件的解决冲突,但是不清楚“favor of “2.””是什么意思。

我测试了一下,如果遇到同名文件,解压的文件会覆盖原有的文件。

a.txt 文件内容如下:

hello

a.tar.gz 文件打包了以下目录结构:

.
├── a
│   ├── a.txt
│   └── b.txt
└── a.tar.gz

其中 a.txt 文件内容如下:

hi

创建 Dockerfile 文件如下:

FROM alpine

ADD a.txt a/a.txt

ADD a.tar.gz .

构建:

docker build -t kai0111_5 .

查看

docker run kai0111_5 sh -c "tree a && cat a/a.txt"
a
├── a.txt
└── b.txt

0 directories, 2 files
hi

可见, a.txt 文件内容是tar包里的文件内容。

注:不能写成 docker run kai0111_5 tree a && cat a/a.txt ,否则会被识别为 docker run kai0111_5 tree acat a/a.txt 两条命令的组合。

  • 文件是否被看做压缩文件并自动解压,是由文件内容决定的,而不是由文件的后缀名决定的。
FROM alpine

ADD a.tar.gz .

其中, a.tar.gz 是一个空文件。

构建:

docker build -t kai0111_6 .

查看:

docker run kai0111_6 ls -l
total 12
-rw-r--r--    1 root     root             0 Jan 11 10:48 a.tar.gz
......

可见,仍然是 a.tar.gz ,没有解压。

  • 对于其它类型的文件,则连同元数据一起复制到目标路径。如果 <dest>/ 结尾,则被当作是一个目录, <src> 的内容会被写入该目录。

  • 如果有多个 <src> ,或者使用了通配符,则 <dest> 必须是目录,并以 / 结尾。

  • 如果 <dest> 不是以 / 结尾,则会看作文件名。

  • 如果 <dest> 路径里的目录不存在,则会自动创建。

校验远程文件checksum

语法:

--checksum=<checksum>

例如:

ADD --checksum=sha256:24454f830cdb571e2c4ad15481119c43b3cafd48dd869a9b2945d1036d1dc68d https://mirrors.edge.kernel.org/pub/linux/kernel/Historic/linux-0.01.tar.gz /

注:可用 sha256sum 命令生成checksum:

sha256sum linux-0.01.tar.gz
24454f830cdb571e2c4ad15481119c43b3cafd48dd869a9b2945d1036d1dc68d  linux-0.01.tar.gz

这样,如果文件发生变化,checksum对不上,就会及时报错。

创建 Dockerfile 文件如下:

# syntax=docker/dockerfile:1

FROM alpine

ADD --checksum=sha256:24454f830cdb571e2c4ad15481119c43b3cafd48dd869a9b2945d1036d1dc68d https://mirrors.edge.kernel.org/pub/linux/kernel/Historic/linux-0.01.tar.gz /

注意: # syntax=docker/dockerfile:1 这一行不能缺少,否则会报错:

ERROR: failed to solve: instruction 'ADD --checksum=<CHECKSUM>' requires the labs channel

构建:

docker build -t kai0111_7 .

查看:

docker run kai0111_7 ls -l    
-rw-------    1 root     root         73091 Oct 30  1993 linux-0.01.tar.gz
......

如果文件发生了变化(此处用checksum变化来模拟文件变化,总之都是二者不匹配),构建时报错:

docker build -t kai0111_8 .
......
ERROR: failed to solve: digest mismatch sha256:24454f830cdb571e2c4ad15481119c43b3cafd48dd869a9b2945d1036d1dc68d: sha256:e97d6191145d306c052bb0205e66ad507e934f189edd1b7f58f17ab1a7387fd0

--checksum 只支持HTTP来源的文件。若是本地文件,则会报错。

创建 Dockerfile 文件如下:

# syntax=docker/dockerfile:1

FROM alpine

ADD --checksum=sha256:24454f830cdb571e2c4ad15481119c43b3cafd48dd869a9b2945d1036d1dc68d linux-0.01.tar.gz /

构建,报错如下:

docker build -t kai0111_9 .
......
ERROR: failed to solve: checksum can't be specified for non-HTTP sources

添加Git仓库

语法:

ADD [--keep-git-dir=<boolean>] <git ref> <dir>

例如:

# syntax=docker/dockerfile:1
FROM alpine
ADD --keep-git-dir=true https://github.com/moby/buildkit.git#v0.10.1 /buildkit

注意: # syntax=docker/dockerfile:1 这一行不能缺少,否则会报错:

ERROR: failed to solve: instruction ADD <git ref> requires the labs channel

构建:

docker build -t kai0111_10 .

查看:

docker run kai0111_10 tree -L 1 /buildkit

注:结果很长,不在这里展示了。tree 命令的 -L 1 选项(只显示一层结构)无效,可能是因为alpine image里的 tree 命令做了简化。

--keep-git-dir=true 选项表示是否添加 .git 目录,其默认值是false。

添加私有Git仓库

语法:

ADD git@git.example.com:foo/bar.git /bar

git.weixin.qq.com 为例。

首先在网站上配置好ssh key,确认git clone没有问题:

git clone git@git.weixin.qq.com:dukeding/test0522.git
Cloning into 'test0522'...
sign_and_send_pubkey: signing failed for RSA "/home/ding/.ssh/id_rsa" from agent: agent refused operation
remote: Total 101 (delta 0), reused 101 (delta 0)
Receiving objects: 100% (101/101), 617.38 KiB | 666.00 KiB/s, done.
Resolving deltas: 100% (33/33), done.

创建 Dockerfile 文件如下:

# syntax=docker/dockerfile:1

FROM alpine

ADD git@git.weixin.qq.com:dukeding/test0522.git /dir1

构建:

docker build --ssh default -t kai0111_11 .

注意:多了 --ssh default 选项,如果不加该选项,会报错:

ERROR: failed to solve: failed to load cache key: unset ssh forward key default

查看:

docker run kai0111_11 ls -l dir1
total 16
-rw-r--r--    1 root     root           583 Jan 11 13:41 README.md
drwxr-xr-x    4 root     root            52 Jan 11 13:41 cloudfunctions
drwxr-xr-x    5 root     root           135 Jan 11 13:41 miniprogram
-rw-r--r--    1 root     root          1847 Jan 11 13:41 project.config.json
-rw-r--r--    1 root     root          1722 Jan 11 13:41 project.private.config.json
-rw-r--r--    1 root     root           223 Jan 11 13:41 uploadCloudFunction.bat

注:在RedHat 9.3中,遇到问题:

问题1:

Unable to negotiate with **** port 22: no matching host key type found. Their offer: ssh-rsa

解决方法:

创建 ~/.ssh/config 文件,权限是600。

Host *
HostkeyAlgorithms +ssh-rsa
PubkeyAcceptedKeyTypes +ssh-rsa

参考:

  • https://www.jianshu.com/p/764249229bc4

问题2:

ssh_dispatch_run_fatal: Connection to 212.64.118.180 port 22: error in libcrypto

解决办法:

sudo update-crypto-policies --show
sudo update-crypto-policies --set DEFAULT:SHA1

参考:

  • https://zhuanlan.zhihu.com/p/557504425
  • https://access.redhat.com/articles/3666211

ADD --link

参见我另一篇文档( https://blog.csdn.net/duke_ding2/article/details/135543261 )。

注意:该功能貌似目前被disable了。

COPY

COPY 指令和 ADD 指令大同小异。其差别在于, COPY 指令只能复制本地文件,无法复制远程文件,比如URL远程文件和Git远程仓库。另外, ADD 指令在添加本地文件时,会自动把tar文件解压。

此外, --parent 选项只能用于 COPY ,不能用于 ADD

COPY --parent

语法:

COPY [--parents[=<boolean>]] <src>... <dest>

创建 Dockerfile 文件如下:

# syntax=docker/dockerfile-upstream:master-labs

FROM alpine

WORKDIR mydir

COPY ./xxx/yyy/a.txt ./xxx/zzz/a.txt dir1/

COPY --parents ./xxx/yyy/a.txt ./xxx/zzz/a.txt dir2/

构建:

docker build -t kai0111_12 .

查看:

docker run kai0111_12 tree .
.
├── dir1
│   └── a.txt
└── dir2
    └── xxx
        ├── yyy
        │   └── a.txt
        └── zzz
            └── a.txt

5 directories, 3 files

其中, dir1/a.txt./xxx/zzz/a.txt

注:其行为类似于Linux cp 命令的 --parents 选项:

cp --parents xxx/yyy/a.txt xxx/zzz/a.txt /tmp/

tree /tmp/xxx    
/tmp/xxx
├── yyy
│   └── a.txt
└── zzz
    └── a.txt

但有一点不同:如果没有 --parents 选项,则文件冲突时, cp 命令会报错:

cp xxx/yyy/a.txt xxx/zzz/a.txt /tmp/ 
cp: will not overwrite just-created '/tmp/a.txt' with 'xxx/zzz/a.txt'

COPY 指令则会覆盖文件。

通常layer的数量越少越好,所以可以利用 --parents ,尽量少用 COPY 指令,在指令里包含多个 <src> ,保持目标文件和源文件结构一致。

注: --parents 只对 COPY 有效,在 ADD 指令里使用 --parents 会报错:

ERROR: failed to solve: dockerfile parse error on line 9: unknown flag: parents

使用ADD还是COPY?

前面提到,二者的区别在于, COPY 指令只能复制本地文件,无法复制远程文件,比如URL远程文件和Git远程仓库。另外, ADD 指令在添加本地文件时,会自动把tar文件解压。那么,一般情况下,使用时该如何选择呢?

我的理解是: COPY 包含最基本的“文件复制”功能,而 ADD 包含一些“高级”功能。因此,如果是“高级”用法,则只能用 ADD ,而如果是常规的文件复制,则用 COPY (直观,没有歧义)。

参考

  • https://docs.docker.com/engine/reference/builder
  • https://docs.docker.com/develop/develop-images/instructions/#add-or-copy
文章来源:https://blog.csdn.net/duke_ding2/article/details/135517264
本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。