Dockerfile文件说明
Dockerfile #生成的镜像的时候会在执行命 令的当前目录查找 Dockerfile 文件,所以名称不可写错,而且 D 必须大写
如果在从该镜像启动容器的时候也指定了命令(如 docker run -d nginx nginx),那么(nginx)指定的命令会覆盖 Dockerfile 构建的镜像里面的 CMD 命令,即指定的命令优先级更高,Dockerfile 的 优先级较低一些,重新指定的指令优先级要高一些。
除了注释行之外的第一行,必须是 From。
一般Dockerfile分为四部分:基础镜像信息、维护者信息、镜像操作指令和容器启动时执行指令。
Dockerfile指令 FROM MAINTRINER RUN EXPOSE CMD ENTRYPOINT WORKDIR ENV USER VOLUME ADD COPY LABEL STOPSIGNAL ARG ONBUILD HEALTHCHECK 一、From #第一行先定义基础镜像,后面的本地有效的镜像名,如果本地没 有会从远程仓库下载. FROM nginx 二、MAINTAINER #镜像维护者的信息 MAINTAINER Jack.Zhang 123456@qq.com 三、LABEL #将元数据添加到镜像。一个镜像可以有多个标签。 基础镜像或父镜像(FROM行中的图像)中包含的标签由镜像继承。如果一个标签已经存在,但是有不同的值,那么最近应用的值将覆盖以前设置的任何值。 两种方法 LABEL multi.label1="value1" multi.label2="value2" other="value3" LABEL multi.label1="value1" \ multi.label2="value2" \ other="value3" 四、USER #指定该容器运行时的用户名和 UID,后续的 RUN 命令也会使用这面指定的用户执行 USER user USER user:group USER uid USER uid:gid 五、WORKDIR 指令用来执行容器运行时,执行容器运行时的工作目录,ENTRYPOINT指令和/和CMD指令运行的命令就是在这个指令所指定的工作目录下执行的 #指定工作目录,最终工作路径为/a/b WORKDIR /a WORKDIR b 一个Dockerfile如下所示,其将容器的工作路径切换到/opt/webapp/db目录下,然后执行了RUN指令 WORKDIR /opt/webapp/db RUN bundle install 指定多个WORKDIR,表示在构建镜像时切换工作目录。例如,下面构建镜像时,先将工作目录切换到/opt/webapp/db执行RUN指令,然后再将工作目录切换到/opt/webapp执行rackup命令 WORKDIR /opt/webapp/db RUN bundle install WORKDIR /opt/webapp/ ENTRYPOINT [ "rackup" ] 六、VOLUME #将容器中的一个或多个目录挂载到宿主机中/var/lib/docker/volumes/, 删除容器时文件也存在 ,常用于日志。 docker inspect 容器ID | grep volume 或 docker inspect 挂载目录ID VOLUME ["/dir_1", "/dir_2" ..] docker run -it -v /date/:/date nginx 两者区别在于,VOLUME挂载到宿主机中/var/lib/docker/volumes/ 是固定的, 而-v 是可自定义路径。 从Dockerfile中更改卷:如果任何构建步骤在卷声明后更改了数据,这些更改将被丢弃。 JSON格式:列表被解析为一个JSON数组。必须用双引号(")括起来,而不是单引号(')。 宿主目录是在容器运行时声明的:宿主目录(挂载点)本质上依赖于宿主。这是为了保持映像的可移植性,因为不能保证给定的主机目录在所有主机上都可用。由于这个原因,您不能从Dockerfile中挂载主机目录。卷指令不支持指定host-dir参数。创建或运行容器时,必须指定挂载点 下面这条指令会为基于此镜像创建的任何容器创建一个名为/opt/project的挂载点 VOLUME ["/opt/project"] 也可以以数组的形式指定多个卷。例如: VOLUME ["/opt/project", "/data"] 七、ENV #设置容器变量,常用于想容器内传递用户密码等 从Docker 1.4开始,可以指定多个环境变量。例如,下面设置了两个环境变量: ENV RVM_PATH=/home/rmv RVM_ARCHFLAGS="-arch i386" 1一些注意点: 当设置了环境变量之后,后面的任何RUN指令等操作都是基于这个环境变量进行操作 如果需要,可以通过在环境变量前加上一个反斜线来进行转义 该指令设置的环境变量,会在镜像中永久保存,因此基于该镜像创建的任何容器都会使用这个环境变量 -e选项 默认情况下,ENV设置的环境变量是在镜像中永久保存的,因此每次创建容器,进入系统之后变量都会在系统中 "docker run"的-e选项可以指定:只在本次创建的容器运行时有效,容器退出或删除后环境变量失效 例如,下面启动时指定容器的WEN_PORT环境变量为8080,当该容器退出后变量失效 sudo docker run -it -e "WEB_PORT=8080" dongshao/images 环境变量默认会在镜像中持久化。如果仅在构建过程中需要环境变量,而在最终映像中则不需要,则执行如下: RUN DEBIAN_FRONTEND=noninteractive apt-get update && apt-get install -y ... 或者使用ARG,它不会在最终镜像中持久化 ARG DEBIAN_FRONTEND=noninteractive RUN apt-get update && apt-get install -y ... 七、ADD #指令用来将Dockerfile构建环境下的文件和目录复制到镜像中, 格式说明: 在ADD文件时,Docker通过目的地址(后面的路径)参数末尾的字符来判断文件源(前面的路径)是目录还是文件 如果目标地址以/结尾,那么Docker就认为源位置指向的是一个目录,那么会在文件源放置在这个目录下 如果目标地址不是以/结尾,那么Docker就认为源位置指向的是文件,那么表示将文件源拷贝成该指定的文件 压缩文件会自动解压 ADD遵循以下规则: <src>路径必须在构建的上下文中;你不能添加../某物/某物,因为docker构建的第一步是发送上下文目录(和子目录)给docker守护进程。 如果<src>是一个URL,而<dest>不以末尾的斜杠结束,则从URL下载一个文件并复制到<dest>。 如果<src>是一个URL,而<dest>确实以一个末尾的斜杠结尾,那么文件名将从URL推断出来,文件将被下载到<dest>/<filename>。例如,添加http://example.com/foobar /将创建文件/foobar。URL必须有一个重要的路径,这样才能在这种情况下发现合适的文件名(http://example.com不起作用)。 如果<src>是一个目录,则复制该目录的全部内容,包括文件系统元数据。目录本身不会被复制,只是它的内容。 如果<src>是一个以可识别的压缩格式(identity、gzip、bzip2或xz)的本地tar存档,那么它将被解压缩为一个目录。远程url中的资源不会解压缩。识别为可识别的压缩格式完全取决于文件的内容,而不是文件的名称。解压缩行为与tar -x相同。 如果<src>是任何其他类型的文件,它会和它的元数据一起单独复制。在这种情况下,如果<dest>以一个末尾的斜杠/结束,它将被认为是一个目录,并且<src>的内容将写入<dest>/base(<src>)。 如果指定了多个<src>资源,或者直接指定,或者使用通配符指定,那么<dest>必须是一个目录,并且必须以斜杠/结束。 如果<dest>没有以尾斜杠结束,它将被认为是一个常规文件,并且<src>的内容将写入<dest>。 如果<dest>不存在,将在其路径中创建所有缺失的目录(目标路径可以不存在,由ADD创建路径)。 # 添加所有以“hom”开头的文件: ADD hom* /mydir/ # 添加所有hom开头的txt文件 ?被替换为任何单个字符,例如,“home.txt” ADD hom?.txt /mydir/ # <dest>是一个相对路径,或一个相对于WORKDIR的路径,源文件将被复制到目标容器中。 # 添加test.txt文件到 <WORKDIR>/relativeDir/ ADD test.txt relativeDir/ # <dest>是一个绝对路径,添加test.txt文件到 /absoluteDir/ ADD test.txt /absoluteDir/ # 添加包含特殊字符的文件,arr[0].txt ADD arr[[]0].txt /mydir/
添加上容器的文件权限自动设置为 600 ,如果需要更改权限,需要在增加一层 RUN 进行权限修改。
如果下载的是一个压缩包,一般情况是 RUN 指令,然后使用wget或curl去下载,然后更改权限、解压、清理下载的源文件。
需要注意的是,ADD 指令会令镜像构建缓存失效,从而可能会镜像构建变得非常缓慢。
在使用 ADD 指令的时候,还可以加上 --chown=<user>:<group> 选项来改变文件的所属用户和所属组。
ADD --chown=55:mygroup files* /mydir/
ADD --chown=bin files* /mydir/
ADD --chown=1 files* /mydir/
ADD --chown=10:11 files* /mydir/
七、EXPOSE #仅仅只是声明向外开放的端口,多个端口用空格做间隔,启动容器时候-p需要使用此端向外映射,如:-p 8081:80 帮助镜像使用者理解这个镜像服务的守护端口,以方便配置映射。 在运行时使用随机端口映射时,也就是 docker run -P 时,会自动随机映射 EXPOSE 的端口。 EXPOSE 80 443 八、COPY #非常类似于ADD指令,它们根本的不同是COPY只关心在构建上下文中复制本地文件,而不会去做文件提取(extraction)和解压(decomposition)的工作 相关说明: 文件源路径必须是一个与当前构建环境相对的文件或者目录,本地文件都放到和Dockerfile同一个目录下。不能复制该目录之外的任何文件,因为构建环境将会上传到Docker守护进程,而复制是在Docker守护进程中运行的。任何位于构建环境之外的东西都是不可用的 COPY指令的目的位置必须是容器内部的一个绝对路径 与ADD指令一样,如果目的位置不存在,那么也会进行创建,任何由该指令创建的文件或者目录的UID和GID都会设置为0 如果源路径是一个目录,那么这个目录将整个复制到容器中,包括文件系统元数据 如果源路径是一个文件,则该文件会随同元数据一起被复制 例如: 下面是将宿主机当前构建目录下的conf.d/目录拷贝到镜像的/etc/apache2/目录下 COPY conf.d/ /etc/apache2/ 九、STOPSIGNAL 这个指令笔者也并不常用,我查询了docker-nginx在github上的issues里面给出的答案是,之所以加上STOPSIGNAL信号的目的是:避免docker容器停止后,nginx服务不能正确终止造成僵尸进程的存在。 STOPSIGNAL指令用来设置停止容器时发送什么系统调用信号给容器 这个信号必须是内核系统调用表中合法的数,比如9,或者SIGNAME格式中的信号名称,如SIGKILL STOPSIGNAL指令是在Docker 1.9版本中引入的 STOPSIGNAL SIGQUIT 停止信号,类似kill -9。 STOPSIGNAL signal 十、ARG: 构建参数,与 ENV 作用一致。不过作用域不一样。ARG 设置的环境变量仅对 Dockerfile 内有效,也就是说只有 docker build 的过程中有效,构建好的镜像内不存在此环境变量。 可以使用ARG或ENV指令来指定RUN指令可用的变量,使用ENV指令定义的环境变量总是覆盖同名的ARG指令。 不建议在构建镜像时使用-build-arg参数来传递如密钥,用户凭据等信息,因为使用docker history命令可以看得到 构建命令 docker build 中可以用 --build-arg <参数名>=<值> 来覆盖。 如果ARG指令有一个默认值,并且在构建时没有传递值,构建器使用默认值。 ARG user1=someuser 十一、ONBUILD #为镜像添加一个触发器指令,以便在稍后将该映像用作另一个构建的基础时执行。触发器将在下游构建的上下文中执行,就像它在下游Dockerfile中的FROM指令之后被立即插入一样。 任何构建指令都可以注册为触发器。 ONBUILD <INSTRUCTION> 如果正在构建一个映像,它将作为构建其他映像的基础,那么这是非常有用的。 不允许使用ONBUILD ONBUILD链接ONBUILD指令。 十一、HEALTHCHECK #检查运行状况。在一定数量的连续失败之后,它就变得不健康。 Dockerfile中只有最后一个健康检查才会生效。 HEALTHCHECK NONE(禁用从基本映像继承的任何健康检查) HEALTHCHECK [OPTIONS] CMD command (通过在容器中运行命令来检查容器的健康状况) 在CMD之前可以显示的选项是: --interval = DURATION(时间间隔=持续;默认值:30s) --timeout = DURATION(超时=持续;默认值:30s) --start-period = DURATION(开始时间;默认值:0s) --retries = N(重试;默认值:3) CMD关键字后面的命令可以是shell命令或exec数组。 健康状态码: 0:成功—容器运行良好,可以使用 1:不健康—容器不能正常工作 2:保留-不使用此退出码 示例:# 每隔五分钟左右检查一下网络服务器是否能在三秒内提供网站主页,当容器的健康状态发生变化时,将生成一个带有新状态的health_status事件。 HEALTHCHECK --interval=5m --timeout=3s CMD curl -f http://localhost/ || exit 1 十二、RUN #用于执行后面跟着的命令行命令,有以下俩种格式: 1.shell 格式: RUN <命令行命令> # <命令行命令> 等同于,在终端操作的 shell 命令。 2.exec 格式: RUN ["可执行文件", "参数1", "参数2"] # 例如: # RUN ["./test.php", "dev", "offline"] 等价于 RUN ./test.php dev offline 注意:Dockerfile 的指令每执行一次都会在 docker 上新建一层。所以过多无意义的层,会造成镜像膨胀过大。例如: FROM centos RUN yum -y install wget RUN wget -O redis.tar.gz "http://download.redis.io/releases/redis-5.0.3.tar.gz" RUN tar -xvf redis.tar.gz 以上执行会创建 3 层镜像。可简化为以下格式: FROM centos RUN yum -y install wget \ && wget -O redis.tar.gz "http://download.redis.io/releases/redis-5.0.3.tar.gz" \ && tar -xvf redis.tar.gz 如上,以 && 符号连接命令,这样执行后,只会创建 1 层镜像。 十三、ENTRYPOINT 一个Dockerfile中如果定义多个ENTRYPOINT,只有最后一条ENTRYPOINT生效,并且每次启动docker容器,都会执行ENTRYPOINT指定的脚本。对于nginx:1.20.2而言,"/docker-entrypoint.sh"脚本中定义了nginx配置检查及nginx服务的启动指令。所以通常情况下ENTRYPOINT指定的脚本通常都是镜像内核心服务的启动脚本。 ENTRYPOINT ["/docker-entrypoint.sh"] 这个脚本中最后执行了nginx服务启动,需要配合CMD命令完成,参考下文的CMD命令。如: ENTRYPOINT ["nginx"] CMD ["-g","daemon off;"] 十四、CMD ["nginx","-g","daemon off;"] #运行的命令,每个 Dockerfile 只能有一条, 如果有多条则只有最后一条被执行 . CMD指令也是用来执行linux命令或脚本,这一点和RUN指令是一致的。二者的区别在于 CMD指令是在执行docker run指令时被执行,也就是创建容器时执行;而RUN指令实在镜像构建的时候执行,即docker bulid时候执行。 也正因为指令的执行期不同,RUN命名执行的写入操作被写入到镜像层。CMD指令执行结果包含写入操作,被写入到容器层。(可以参考我之前文章《镜像分层原理》学习理解)。 所以CMD和ENTRYPOINT 指令有点相似,都是在创建容器时运行。需要注意的是:一旦Dockerfile中包含ENTRYPOINT指令,CMD指令就作为ENTRYPOINT指定的脚本的参数存在。参考下文格式语法。 CMD包含三种格式: 第一种为ENTRYPOINT指定的脚本传参,上文中的ENTRYPOINT脚本最后一行的exec "$@"就是在执行CMD传递的命令及参数。来完成nginx服务的启动。这种用法是各官方Dockerfile常用的做法(如:nginx、redis),就是在ENTRYPOINT指定的脚本中做一些配置准备工作, 然后在ENTRYPOINT脚本的最后一行通过exec "$@"调用CMD命令进行容器服务的启动。 CMD ["nginx", "-g", "daemon off;"] 第二种 执行一个命令或shell脚本,可以传参,注意是双引号。与第一种格式语法是一样的,只是没有ENTRYPOINT指定脚本,所以不作为ENTRYPOINT指定脚本的参数存在。 CMD ["executable","param1","param2"] 第三种为普通的执行shell脚本的语法,如执行echo "This is a test." | wc -cshell命令行。笔者说:在Dockerfile中这种方法不要用,没必要知道为什么。 CMD echo "This is a test." | wc -c CMD指令与"docker run"的关系: 我们知道,在执行"docker run"让容器运行时,可以在"docker run"命令的最后加上命令让容器去运行 CMD指令与上面的功能是一样的。不过"docker run"的命令会覆盖CMD指令 SHELL形式与EXEC形式的注意事项1 Dockerfile的RUN、CMD、ENTRYPOINT等指令可以用来执行命令。执行命令的方式有2种: shell形式(默认方式):默认的情况下,Docker是使用shell来执行这些命令的,例如ENTRYPOINT node app.js exec形式:如果你想要以exec形式运行命令,那么可以使用数组的形式运行命令,例如,如ENTRYPOINT["node","app.js"] shell形式与exec形式有什么区别: shell:会启动一个shell,然后在shell上面运行程序。也就是说,该形式运行的命令,会自动在前面加上/bin/sh -c exec:不启动shell,直接运行程序 如果在一个不支持shell的平台上运行或者不希望在shell中运行(比如避免shell字符串篡改),也可以使用exec格式的RUN指令 例如,下面以exec形式运行ENTRYPOINT指令: ENTRYPOINT["node","app.js"] 之后查看容器中的运行进程列表,可以看到这里是直接运行node进程,而并非在shell中执行 docker exec dfg8sga ps x 如果采用shell形式(ENTRYPOINT node app.js),容器进程如下所示,可以看到其是在shell上运行的程序 docker exec -it dfg8sga ps x SHELL形式与EXEC形式的注意事项2 但是当涉及到的命令涉及一些变量时,那么EXEC是不能识别常量的。于是就需要使用shell形式来执行 例如,执行下面的ENTRYPOINT指令,那么容器运行时,打印的是"hello &name",不会识别出name变量 FROM ubuntu ENV name Docker ENTRYPOINT ["/bin/echo", "hello $name"] 对于上面问题的更改就是改用/bin/bash来执行,例如下面就是正确的了,最终会打印"hello Docker" FROM ubuntu ENV name Docker ENTRYPOINT ["/bin/bash", "-c", "echo hello $name"]