文章目录
- 环境
- ADD
- 规则
- 校验远程文件checksum
- 添加Git仓库
- 添加私有Git仓库
- `ADD --link`
- COPY
- `COPY --parent`
- 使用ADD还是COPY?
- 参考
环境
- 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 wget
、RUN 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包(比如identity
,gzip
,bzip2
,xz
),则会被解压为一个目录。但如果<src>
是远程文件,则不会解压。当目录被复制或解压时,其行为和tar -x
是相同的。结果是以下的结合:- Whatever existed at the destination path and
- 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 alpineADD a.txt a/a.txtADD a.tar.gz .
构建:
docker build -t kai0111_5 .
查看
docker run kai0111_5 sh -c "tree a && cat a/a.txt"
a
├── a.txt
└── b.txt0 directories, 2 files
hi
可见, a.txt
文件内容是tar包里的文件内容。
注:不能写成 docker run kai0111_5 tree a && cat a/a.txt
,否则会被识别为 docker run kai0111_5 tree a
和 cat a/a.txt
两条命令的组合。
- 文件是否被看做压缩文件并自动解压,是由文件内容决定的,而不是由文件的后缀名决定的。
FROM alpineADD 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:1FROM alpineADD --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:1FROM alpineADD --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:1FROM alpineADD 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-labsFROM alpineWORKDIR mydirCOPY ./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.txt5 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