除了 FROM
,RUN
,还提及了 COPY
, ADD
,其实 Dockerfile 功能很强大,它提供了十多个指令。下面我们继续讲解其他的指令。
格式:
COPY ...
COPY ["",... ""]
和 RUN
指令一样,也有两种格式,一种类似于命令行,一种类似于函数调用。
COPY
指令将从构建上下文目录中 的文件/目录复制到新的一层的镜像内的
位置。比如:
COPY package.json /usr/src/app/
可以是多个,甚至可以是通配符,其通配符规则要满足 Go 的
filepath.Match
规则,如:
COPY hom* /mydir/
COPY hom?.txt /mydir/
可以是容器内的绝对路径,也可以是相对于工作目录的相对路径(工作目录可以用
WORKDIR
指令来指定)。目标路径不需要事先创建,如果目录不存在会在复制文件前先行创建缺失目录。
此外,还需要注意一点,使用 COPY
指令,源文件的各种元数据都会保留。比如读、写、执行权限、文件变更时间等。这个特性对于镜像定制很有用。特别是构建相关文件都在使用 Git 进行管理的时候。
ADD
指令和 COPY
的格式和性质基本一致。但是在 COPY
基础上增加了一些功能。
比如 可以是一个
URL
,这种情况下,Docker 引擎会试图去下载这个链接的文件放到 去。下载后的文件权限自动设置为
600
,如果这并不是想要的权限,那么还需要增加额外的一层 RUN
进行权限调整,另外,如果下载的是个压缩包,需要解压缩,也一样还需要额外的一层 RUN
指令进行解压缩。所以不如直接使用 RUN
指令,然后使用 wget
或者 curl
工具下载,处理权限、解压缩、然后清理无用文件更合理。因此,这个功能其实并不实用,而且不推荐使用。
如果 为一个
tar
压缩文件的话,压缩格式为 gzip
, bzip2
以及 xz
的情况下,ADD
指令将会自动解压缩这个压缩文件到 去。
在某些情况下,这个自动解压缩的功能非常有用,比如官方镜像 ubuntu
中:
FROM scratch
ADD ubuntu-xenial-core-cloudimg-amd64-root.tar.gz /
但在某些情况下,如果我们真的是希望复制个压缩文件进去,而不解压缩,这时就不可以使用 ADD
命令了。
在 Docker 官方的 Dockerfile 最佳实践文档
中要求,尽可能的使用 COPY
,因为 COPY
的语义很明确,就是复制文件而已,而 ADD
则包含了更复杂的功能,其行为也不一定很清晰。最适合使用 ADD
的场合,就是所提及的需要自动解压缩的场合。
另外需要注意的是,ADD
指令会令镜像构建缓存失效,从而可能会令镜像构建变得比较缓慢。
因此在 COPY
和 ADD
指令中选择的时候,可以遵循这样的原则,所有的文件复制均使用 COPY
指令,仅在需要自动解压缩的场合使用 ADD
。
CMD
指令的格式和 RUN
相似,也是两种格式:
shell
格式:CMD
exec
格式:CMD ["可执行文件", "参数1", "参数2"...]
- 参数列表格式:
CMD ["参数1", "参数2"...]
。在指定了ENTRYPOINT
指令后,用CMD
指定具体的参数。
之前介绍容器的时候曾经说过,Docker 不是虚拟机,容器就是进程。既然是进程,那么在启动容器的时候,需要指定所运行的程序及参数。CMD
指令就是用于指定默认的容器主进程的启动命令的。
在运行时可以指定新的命令来替代镜像设置中的这个默认命令,比如,ubuntu
镜像默认的 CMD
是 /bin/bash
,如果我们直接 docker run -it ubuntu
的话,会直接进入 bash
。我们也可以在运行时指定运行别的命令,如 docker run -it ubuntu cat /etc/os-release
。这就是用 cat /etc/os-release
命令替换了默认的 /bin/bash
命令了,输出了系统版本信息。
在指令格式上,一般推荐使用 exec
格式,这类格式在解析时会被解析为 JSON 数组,因此一定要使用双引号 "
,而不要使用单引号。
如果使用 shell
格式的话,实际的命令会被包装为 sh -c
的参数的形式进行执行。比如:
CMD echo $HOME
在实际执行中,会将其变更为:
CMD [ "sh", "-c", "echo $HOME" ]
这就是为什么我们可以使用环境变量的原因,因为这些环境变量会被 shell 进行解析处理。
提到 CMD
就不得不提容器中应用在前台执行和后台执行的问题。这是初学者常出现的一个混淆。
Docker 不是虚拟机,容器中的应用都应该以前台执行,而不是像虚拟机、物理机里面那样,用 upstart/systemd 去启动后台服务,容器内没有后台服务的概念。
一些初学者将 CMD
写为:
CMD service nginx start
然后发现容器执行后就立即退出了。甚至在容器内去使用 systemctl
命令结果却发现根本执行不了。这就是因为没有搞明白前台、后台的概念,没有区分容器和虚拟机的差异,依旧在以传统虚拟机的角度去理解容器。
对于容器而言,其启动程序就是容器应用进程,容器就是为了主进程而存在的,主进程退出,容器就失去了存在的意义,从而退出,其它辅助进程不是它需要关心的东西。
而使用 service nginx start
命令,则是希望 upstart 来以后台守护进程形式启动 nginx
服务。而刚才说了 CMD service nginx start
会被理解为 CMD [ "sh", "-c", "service nginx start"]
,因此主进程实际上是 sh
。那么当 service nginx start
命令结束后,sh
也就结束了,sh
作为主进程退出了,自然就会令容器退出。
正确的做法是直接执行 nginx
可执行文件,并且要求以前台形式运行。比如:
CMD ["nginx", "-g", "daemon off;"]
ENTRYPOINT
ENTRYPOINT
的格式和 RUN
指令格式一样,分为 exec
格式和 shell
格式。
ENTRYPOINT
的目的和 CMD
一样,都是在指定容器启动程序及参数。ENTRYPOINT
在运行时也可以替代,不过比 CMD
要略显繁琐,需要通过 docker run
的参数 --entrypoint
来指定。
当指定了 ENTRYPOINT
后,CMD
的含义就发生了改变,不再是直接的运行其命令,而是将 CMD
的内容作为参数传给 ENTRYPOINT
指令,换句话说实际执行时,将变为:
""
那么有了 CMD
后,为什么还要有 ENTRYPOINT
呢?这种 ""
有什么好处么?让我们来看几个场景。
假设我们需要一个得知自己当前公网 IP 的镜像,那么可以先用 CMD
来实现:
FROM ubuntu:16.04
RUN apt-get update \
&& apt-get install -y curl \
&& rm -rf /var/lib/apt/lists/*
CMD [ "curl", "-s", "http://ip.cn" ]
假如我们使用 docker build -t myip .
来构建镜像的话,如果我们需要查询当前公网 IP,只需要执行:
docker run myip
当前 IP:61.148.226.66 来自:北京市 联通
嗯,这么看起来好像可以直接把镜像当做命令使用了,不过命令总有参数,如果我们希望加参数呢?比如从上面的 CMD
中可以看到实质的命令是 curl
,那么如果我们希望显示 HTTP 头信息,就需要加上 -i
参数。那么我们可以直接加 -i
参数给 docker run myip
么?
docker run myip -i
docker: Error response from daemon: invalid header field value "oci runtime error: container_linux.go:247: starting container process caused
关注
打赏
最近更新
- 深拷贝和浅拷贝的区别(重点)
- 【Vue】走进Vue框架世界
- 【云服务器】项目部署—搭建网站—vue电商后台管理系统
- 【React介绍】 一文带你深入React
- 【React】React组件实例的三大属性之state,props,refs(你学废了吗)
- 【脚手架VueCLI】从零开始,创建一个VUE项目
- 【React】深入理解React组件生命周期----图文详解(含代码)
- 【React】DOM的Diffing算法是什么?以及DOM中key的作用----经典面试题
- 【React】1_使用React脚手架创建项目步骤--------详解(含项目结构说明)
- 【React】2_如何使用react脚手架写一个简单的页面?