在使用Dockerfile进行镜像构建是,COPY和ADD指令都可以将本地内容拷贝到镜像中,在具体使用中有哪些特点和限制,在这篇文章中将结合实际的例子进行说明。

构建上下文

在使用docker build命令进行构建的时候,都会有一行Sending build context to Docker daemon的提示信息,提示信息中的"build context"就是构建上下文,提示信息说的是将构建信息发送至Docker daemon,Docker daemon我们知道就是Docker的守护进程,而build context(构建上下文)是什么呢?构建上下文就是docker build命令所指定的构建目录下所有的文件的集合。构建镜像的能力是Docker的守护进程提供的,而触发构建则是Docker的客户端进行的,由于Docker的客户端和Docker守护进程可能会在不同机器上,所以这种机制就是为了保证构建镜像的Docker守护进程能够获取到所需要的本地文件。

命令格式

最为简单的使用命令如下所示:

使用命令:COPY/ADD 源文件或者目录 目的文件或者目录

缺省的当前目录

如果在Dockerfile中使用ADD my.cnf .或者COPY my.cnf .这样的语句的话, . 所指定的缺省的当前目录则是环境变量WORKDIR所指定的目录,两条命令都会将构建上下文中的my.cnf拷贝到WORKDIR所指定的目录中。

使用限制1

无路是COPY还是ADD,一般来说,要添加和拷贝的文件都需要在构建上下文之中,不然则会提示"no such file or directory"的错误。具体示例如下所示:

liumiaocn:dockerfile liumiao$ cat Dockerfile
FROM busybox:latestCOPY /tmp/test .
liumiaocn:dockerfile liumiao$ ls
Dockerfile
liumiaocn:dockerfile liumiao$ touch /tmp/test
liumiaocn:dockerfile liumiao$

在构建上下文之外创建一个文件,然后使用docker build进行构建,会提示找不到此文件

liumiaocn:dockerfile liumiao$ docker build -t test:v1 .
Sending build context to Docker daemon  2.048kB
Step 1/2 : FROM busybox:latest---> 19485c79a9bb
Step 2/2 : COPY /tmp/test .
COPY failed: stat /var/lib/docker/tmp/docker-builder978982846/tmp/test: no such file or directory
liumiaocn:dockerfile liumiao$

ADD也会提示相同错误

liumiaocn:dockerfile liumiao$ cat Dockerfile
FROM busybox:latestADD /tmp/test .
liumiaocn:dockerfile liumiao$ docker build -t test:v1 .
Sending build context to Docker daemon  2.048kB
Step 1/2 : FROM busybox:latest---> 19485c79a9bb
Step 2/2 : ADD /tmp/test .
ADD failed: stat /var/lib/docker/tmp/docker-builder613269509/tmp/test: no such file or directory
liumiaocn:dockerfile liumiao$

使用限制2

无路是COPY还是ADD,当拷贝源指定的为目录时,缺省状态下只会将目录下的文件拷贝过去,需要连同目录一起拷贝时需要在目标中指定目录名称,详细可参看如下Dockerfile示例, 事前准备如下所示:

liumiaocn:dockerfile liumiao$ tree .
.
├── Dockerfile
├── testdir
│   ├── testfile1
│   ├── testfile2
│   └── testfile3
└── testworkdir├── testfile3└── testfile42 directories, 6 files
liumiaocn:dockerfile liumiao$

使用如下Dockerfile进行文件拷贝验证,并进行镜像构建

liumiaocn:dockerfile liumiao$ cat Dockerfile
FROM busybox:latestADD testdir .
ADD testworkdir /tmp/testworkdir
ADD testworkdir /tmp/newdirname
liumiaocn:dockerfile liumiao$
liumiaocn:dockerfile liumiao$ docker build -t test:v2 .
Sending build context to Docker daemon  5.632kB
Step 1/4 : FROM busybox:latest---> 19485c79a9bb
Step 2/4 : ADD testdir .---> 43a7a545cdba
Step 3/4 : ADD testworkdir /tmp/testworkdir---> fc63952baee5
Step 4/4 : ADD testworkdir /tmp/newdirname---> c0be200a34d6
Successfully built c0be200a34d6
Successfully tagged test:v2
liumiaocn:dockerfile liumiao$

使用docker run命令启动容器以便确认结果

liumiaocn:dockerfile liumiao$ docker run --rm -it test:v2 sh
/ # ls
bin        etc        proc       sys        testfile2  tmp        var
dev        home       root       testfile1  testfile3  usr
~ # cd /tmp
/tmp # ls
newdirname   testworkdir
/tmp # ls newdirname/
testfile3  testfile4
/tmp #
/tmp # ls testworkdir/
testfile3  testfile4
/tmp #

可以看到缺省目录为根目录,第一行ADD testdir .的结果导致testdir目录下的三个文件均被拷贝至镜像根目录下(缺省的当前目录),第2行为保留目录的方式,第3行稍作变化进行了同时更改目录名称的操作。
使用COPY会有同样结果,这里将WORKDIR稍作修改,结合使用修改Dockerfile并进行验证,执行结果如下所示

liumiaocn:dockerfile liumiao$ cat Dockerfile
FROM busybox:latestCOPY testdir .
COPY testworkdir /tmp/testworkdir
WORKDIR /usr/local
COPY testworkdir ./newdirname
liumiaocn:dockerfile liumiao$ tree .
.
├── Dockerfile
├── testdir
│   ├── testfile1
│   ├── testfile2
│   └── testfile3
└── testworkdir├── testfile3└── testfile42 directories, 6 files
liumiaocn:dockerfile liumiao$ docker build -t test:v3 .
Sending build context to Docker daemon  5.632kB
Step 1/5 : FROM busybox:latest---> 19485c79a9bb
Step 2/5 : COPY testdir .---> 3c337f30fbec
Step 3/5 : COPY testworkdir /tmp/testworkdir---> ee48f32a6974
Step 4/5 : WORKDIR /usr/local
Removing intermediate container e271d35bd256---> d51afa135050
Step 5/5 : COPY testworkdir ./newdirname---> 41925848d344
Successfully built 41925848d344
Successfully tagged test:v3
liumiaocn:dockerfile liumiao$
liumiaocn:dockerfile liumiao$ docker run --rm -it test:v3 sh
/usr/local # ls
newdirname
/usr/local # ls /tmp
testworkdir
/usr/local # ls /
bin        etc        proc       sys        testfile2  tmp        var
dev        home       root       testfile1  testfile3  usr
/usr/local #

可以看到执行结果与ADD完全一致,需要注意的是WORKDIR起效的地方与Dockerfile设定的位置有关,两个COPY的当前位置一个是在根目录一个是在/usr/local下。

对tar文件的处理

对普通文件或者目录的操作,COPY和ADD基本一致,限制也都相同。而对于tar文件或者tar.gz之类的文件处理,则能看到两者的区别了,首先我们准备相关的tar和tar.gz文件,其文件构成如下所示:

liumiaocn:dockerfile liumiao$ tar tf testworkdir.tar.gz
testworkdir/
testworkdir/testfile3
testworkdir/testfile4
liumiaocn:dockerfile liumiao$ tar tf testdir.tar
testdir/
testdir/testfile1
testdir/testfile2
testdir/testfile3
liumiaocn:dockerfile liumiao$

首先使用COPY命令并构建和确认,可以看到文件正常拷贝到目标目录,没有任何惊喜。

liumiaocn:dockerfile liumiao$ cat Dockerfile
FROM busybox:latestWORKDIR /tmp
COPY testdir.tar .
COPY testworkdir.tar.gz .
liumiaocn:dockerfile liumiao$ docker build -t test:v4 .
Sending build context to Docker daemon  6.656kB
Step 1/4 : FROM busybox:latest---> 19485c79a9bb
Step 2/4 : WORKDIR /tmp
Removing intermediate container ac522708a11f---> c2c0081de0b0
Step 3/4 : COPY testdir.tar .---> 8f81fd097c94
Step 4/4 : COPY testworkdir.tar.gz .---> 88889bce1a3d
Successfully built 88889bce1a3d
Successfully tagged test:v4
liumiaocn:dockerfile liumiao$ docker run --rm -it test:v4 sh
/tmp # ls
testdir.tar         testworkdir.tar.gz
/tmp # exit
liumiaocn:dockerfile liumiao$

然后改为ADD命令并进行确认,执行日志如下所示:

liumiaocn:dockerfile liumiao$ cat Dockerfile
FROM busybox:latestWORKDIR /tmp
ADD testdir.tar .
ADD testworkdir.tar.gz .
liumiaocn:dockerfile liumiao$ docker build -t test:v5 .
Sending build context to Docker daemon  6.656kB
Step 1/4 : FROM busybox:latest---> 19485c79a9bb
Step 2/4 : WORKDIR /tmp---> Using cache---> c2c0081de0b0
Step 3/4 : ADD testdir.tar .---> 3aa868ff6d23
Step 4/4 : ADD testworkdir.tar.gz .---> 2baed95cb350
Successfully built 2baed95cb350
Successfully tagged test:v5
liumiaocn:dockerfile liumiao$ docker run --rm -it test:v5 sh
/tmp # ls
testdir      testworkdir
/tmp # ls testdir
testfile1  testfile2  testfile3
/tmp # ls testworkdir
testfile3  testfile4
/tmp #

可以看到ADD的对象如果是tar或者tar.gz时,构建时会自动进行解压,只是想把文件拷贝过去,没想到帮着给解压了,是不是意外的惊喜。如果不了解这个机制,在后续还加入了解压的命令,自然就会出错了。这种黑魔法如果不是使用者所期待的,那有的时候就是惊吓了,毕竟解压只是一个很简单的操作,不用这么贴心也没有太大关系。压缩包有很多种,比如zip文件,就不会给解开,命令所在镜像中有unzip命令

liumiaocn:dockerfile liumiao$ ls
Dockerfile  testdir.zip
liumiaocn:dockerfile liumiao$ docker build -t test:v6 .
Sending build context to Docker daemon  3.584kB
Step 1/3 : FROM busybox:latest---> 19485c79a9bb
Step 2/3 : WORKDIR /tmp---> Using cache---> c2c0081de0b0
Step 3/3 : ADD testdir.zip .---> 55b0ba77137e
Successfully built 55b0ba77137e
Successfully tagged test:v6
liumiaocn:dockerfile liumiao$ docker run --rm -it test:v6 sh
/tmp # ls
testdir.zip
/tmp # pwd
/tmp
/tmp # which zip
/tmp # which unzip
/bin/unzip
/tmp #

指定URL作为源文件

ADD还有一个较为鸡肋的功能,就是可以像curl或者wget那样,可以将文件下载到指定目录,从ADD的功能考虑,就是将URL的源文件拷贝至目标目录下,比如如下的Dockerfile

liumiaocn:dockerfile liumiao$ cat Dockerfile
FROM busybox:latestWORKDIR /usr/local
ADD http://apache.mirror.cdnetworks.com/maven/maven-3/3.6.2/binaries/apache-maven-3.6.2-bin.tar.gz .
liumiaocn:dockerfile liumiao$
liumiaocn:dockerfile liumiao$ docker build -t test:v6 .
Sending build context to Docker daemon  2.048kB
Step 1/3 : FROM busybox:latest---> 19485c79a9bb
Step 2/3 : WORKDIR /usr/local
Removing intermediate container e210061aece1---> d1abb594e4bb
Step 3/3 : ADD http://apache.mirror.cdnetworks.com/maven/maven-3/3.6.2/binaries/apache-maven-3.6.2-bin.tar.gz .
Downloading [==================================================>]  9.142MB/9.142MB---> f9a3c05e1964
Successfully built f9a3c05e1964
Successfully tagged test:v6
liumiaocn:dockerfile liumiao$
liumiaocn:dockerfile liumiao$ docker run --rm -it test:v6 sh
/usr/local # ls
apache-maven-3.6.2-bin.tar.gz
/usr/local #

但是无论是ADD还是COPY,都会生成一个新的层.

liumiaocn:dockerfile liumiao$ docker history test:v6
IMAGE               CREATED             CREATED BY                                      SIZE                COMMENT
f9a3c05e1964        3 minutes ago       /bin/sh -c #(nop) ADD b7b2bf32bed1c8b064d784…   9.14MB
d1abb594e4bb        3 minutes ago       /bin/sh -c #(nop) WORKDIR /usr/local            0B
19485c79a9bb        2 months ago        /bin/sh -c #(nop)  CMD ["sh"]                   0B
<missing>           2 months ago        /bin/sh -c #(nop) ADD file:9151f4d22f19f41b7…   1.22MB
liumiaocn:dockerfile liumiao$
liumiaocn:dockerfile liumiao$ docker history test:v5
IMAGE               CREATED             CREATED BY                                      SIZE                COMMENT
2baed95cb350        23 minutes ago      /bin/sh -c #(nop) ADD file:7a9a17d0315c64301…   0B
3aa868ff6d23        23 minutes ago      /bin/sh -c #(nop) ADD file:99a88ebf8e66f29a2…   0B
c2c0081de0b0        25 minutes ago      /bin/sh -c #(nop) WORKDIR /tmp                  0B
19485c79a9bb        2 months ago        /bin/sh -c #(nop)  CMD ["sh"]                   0B
<missing>           2 months ago        /bin/sh -c #(nop) ADD file:9151f4d22f19f41b7…   1.22MB
liumiaocn:dockerfile liumiao$

如果只是将文件从远端URL下载至镜像中,一般来说,一定会有后续的解压和删除原始文件的动作,所以一般必然会通过RUN命令结合起来,而使用RUN就可以完全实现的功能这里需要使用ADD和RUN结合才能实现,而且层数也会多一个。

实践原则1: 尽可能使用COPY

ADD实际上是COPY的超集,除去COPY的功能之外还有解压tar包和URL源文件支持等功能,但是解压功能也并非针对于所有的压缩包,拷贝的同时进行解压,对于用户来说还是一个黑箱功能的存在,比如在解压之前如需要进行其他操作或者完成性检查等操作都无法实现,Docker官方的最佳实践中也是建议,除非必须使用ADD的情况,尽可能的使用COPY。

实践原则2: 按照文件的变化频度的可能性进行分层添加

根据文件变化的频度和可能性进行添加,变化频度最低和最不容易变化的添加内容放在最低层,这样做有有如下好处:

  • 能够减少层数的变化
  • 合理使用缓存,提高镜像构建的速度

比如如下构建内容,首次构建信息如下所示,可以看到testfile1到testfile5都是创建了新的层,并没有使用缓存。

liumiaocn:dockerfile liumiao$ ls
Dockerfile testfile1  testfile2  testfile3  testfile4  testfile5
liumiaocn:dockerfile liumiao$ cat Dockerfile
FROM busybox:latestWORKDIR /usr/local
COPY testfile1 .
COPY testfile2 .
COPY testfile3 .
COPY testfile4 .
COPY testfile5 .
liumiaocn:dockerfile liumiao$
liumiaocn:dockerfile liumiao$ docker build -t test:v7 .
Sending build context to Docker daemon  4.608kB
Step 1/7 : FROM busybox:latest---> 19485c79a9bb
Step 2/7 : WORKDIR /usr/local---> Using cache---> d1abb594e4bb
Step 3/7 : COPY testfile1 .---> fe55ef082bd6
Step 4/7 : COPY testfile2 .---> 35ae0f479e7b
Step 5/7 : COPY testfile3 .---> 2548899f3cd2
Step 6/7 : COPY testfile4 .---> d446641e7071
Step 7/7 : COPY testfile5 .---> 7dfab6c86f2b
Successfully built 7dfab6c86f2b
Successfully tagged test:v7
liumiaocn:dockerfile liumiao$

假设testfile4所在的层发生了变化,重新构建的时候会发现,原来testfile1-testfile3所在的层都未发生变化,只有testfile4和testfile5发生了变化。构建的时候也直接使用了缓存,在testfile1-testfile3内容很大的时候能很好地提高构建的效率。详细如下所示

liumiaocn:dockerfile liumiao$ docker build -t test:v7 .
Sending build context to Docker daemon   5.12kB
Step 1/7 : FROM busybox:latest---> 19485c79a9bb
Step 2/7 : WORKDIR /usr/local---> Using cache---> 57300405bf4d
Step 3/7 : COPY testfile1 .---> Using cache---> afe826dc92bf
Step 4/7 : COPY testfile2 .---> Using cache---> 17fe013367a0
Step 5/7 : COPY testfile3 .---> Using cache---> 3a80bf8883ce
Step 6/7 : COPY testfile4 .---> af00236f1ac5
Step 7/7 : COPY testfile5 .---> 852902540610
Successfully built 852902540610
Successfully tagged test:v7
liumiaocn:dockerfile liumiao$

Dockerfile实践指南之COPY vs ADD相关推荐

  1. Dockerfile实践指南之RUN命令使用

    使用Dockerfile进行镜像构建,自然离不开RUN命令,相较于docker run的run命令,Dockerfile中的RUN是镜像创建阶段使用的命令,而docker run则是使用镜像启动容器阶 ...

  2. (转) Dockerfile 中的 COPY 与 ADD 命令

    原文:https://www.cnblogs.com/sparkdev/p/9573248.html Dockerfile 中提供了两个非常相似的命令 COPY 和 ADD,本文尝试解释这两个命令的基 ...

  3. (转) Dockerfile 中的 COPY 与 ADD 命令 1

    原文:https://www.cnblogs.com/sparkdev/p/9573248.html Dockerfile 中提供了两个非常相似的命令 COPY 和 ADD,本文尝试解释这两个命令的基 ...

  4. DockerFile : COPY 和 ADD 命令不能拷贝上下文之外的本地文件

    对于 COPY 和 ADD 命令来说,如果要把本地的文件拷贝到镜像中,那么本地的文件必须是在上下文目录中的文件.其实这一点很好解释,因为在执行 build 命令时,docker 客户端会把上下文中的所 ...

  5. Dockerfile实践优化建议

    本文讲的是Dockerfile实践优化建议[编者的话]Dockerfile是一种被Docker程序解释的脚本,Dockerfile由一条一条的指令组成,每条指令对应Linux下面的一条命令.Docke ...

  6. ASP.NET Core Web API 最佳实践指南

    原文地址: ASP.NET-Core-Web-API-Best-Practices-Guide 介绍 当我们编写一个项目的时候,我们的主要目标是使它能如期运行,并尽可能地满足所有用户需求. 但是,你难 ...

  7. ROS机器人操作系统最佳实践指南

    ----ROS Best Practices:https://github.com/ethz-asl/ros_best_practices/wiki---- 这是使用机器人操作系统(ROS)的最佳实践 ...

  8. dita最佳实践指南_艺术资产–最佳做法指南

    dita最佳实践指南 Unity支持来自各种程序或来源的纹理3D模型. 该简短指南由游戏美术师和Unity开发人员共同整理,以帮助您创建在Unity项目中可以更好,更高效地工作的资产. 这将在适当时候 ...

  9. 如何提升本地开发联调效率|阿里巴巴DevOps实践指南

    编者按:本文源自阿里云云效团队出品的<阿里巴巴DevOps实践指南>,前往:https://developer.aliyun.com/topic/devops,下载完整版电子书,了解阿里十 ...

最新文章

  1. windows下的NTP服务
  2. idea中连接mysql插入成功数据 在navicat中刷新表格没有数据_MySQL入门简记
  3. Java 面向对象:static的理解
  4. android 判断照片清晰度_手机如何拍出更清晰的照片?带你走进变焦与对焦的世界...
  5. lua4.0中实现% 取余操作
  6. Mbs Framework 简介
  7. SQL Server2005+SQL Server2000下载
  8. 2018国家网络安全宣传周系列漫画
  9. PR转场预设 鼠标拖拽视频画面滑动转场特效PR预设
  10. 从总线式以太网到SDN交换机OpenVSwitch
  11. vmware win7 iso 镜像文件下载
  12. 刚刚想起猴子布丁,查了点资料,自己实践了下,记录汇总下。
  13. 如果身处历史,你会怎么选?-- 舍不得读完的中国史
  14. 适配 Android N 需要注意什么
  15. Android Studio提示 Cannot load key store: Keystore was tampered with, or password was incorre
  16. 代码“可读性”到底有多重要?
  17. idea设置单行注释格式(包括配置文件)
  18. Unity 灯光设置——灯光类型
  19. C++--数值的整数次方
  20. Win32 ListBox控件

热门文章

  1. 段永平-雪球专刊·段永平投资问答录(上册 商业逻辑篇)(一)
  2. CUMTOJ算法作业二
  3. php常用的数组相关的函数及面向对象
  4. 在公司用手机通过4G网络上网,上网内容可能被公司监控吗?
  5. TCL电子2020财报:漂亮数据之下,AIoT新故事讲得如何?
  6. 借道大数据 互联网基金再探“蓝海”
  7. java实现日历签到功能_[java] 可视化日历的实现(基于Calendar类 )
  8. AtCoder ABC161 E - Yutori
  9. 报错:attributes are not compatible with the provided attributes
  10. Cortex-M3与Cortex-M4的比较