深入Dockerfile(一): 语法指南(转)
最近在学习K8S,发现这两篇文章还不错,转了过来
docker官方文档Dockerfile reference的笔记。
一、机制
1.1 构建
docker构建一个镜像,需要:
- Dockerfile文件
- 构建所需的上下文
$ docker build .
这条命令中,docker CLI会:
- 把当前目录及子目录当做上下文传递给docker服务
- 从当前目录(不包括子目录)中找到Dockerfile
- 检查Dockerfile的语法
- 依次执行Dockerfile中的指令,根据指令生成中间过度镜像(存储在本地,为之后的指令或构建作缓存)
当然也可以用远程git仓库来代替本地路径,docker服务会把整个git仓库(包括git子模块)当做上下文,在仓库的根目录中寻找Dockerfile。
注意
为了加快构建速度,减少传递给docker服务的文件数量,最好将Dockerfile放在单独的空目录中。如果目录中含有大量文件,可以使用**.dockerignore**来忽略构建时用不到的文件。
另一个例子:
$ docker build --no-cache=true -f /path/to/Dockerfile -t some_tag -t image_name:image_version /path/to/build
--no-cache:不使用缓存,每条指令都重新生成镜像(速度会很慢)-f:明确指定Dockerfile-t:给生成的镜像打上标签
1.2 寻找缓存的逻辑
docker寻找缓存的逻辑其实就是树型结构根据Dockerfile指令遍历子节点的过程。下图可以说明这个逻辑。
FROM base_image:version Dockerfile:
+----------+ FROM base_image:version
|base image| RUN cmd1 --> use cache because we found base image
+-----X----+ RUN cmd11 --> use cache because we found cmd1
/ \
/ \
RUN cmd1 RUN cmd2 Dockerfile:
+------+ +------+ FROM base_image:version
|image1| |image2| RUN cmd2 --> use cache because we found base image
+---X--+ +------+ RUN cmd21 --> not use cache because there's no child node
/ \ running cmd21, so we build a new image here
/ \
RUN cmd11 RUN cmd12
+-------+ +-------+
|image11| |image12|
+-------+ +-------+
大部分指令可以根据上述逻辑去寻找缓存,除了ADD和COPY。
这两个指令会复制文件内容到镜像内,除了指令相同以外,docker还会检查每个文件内容校验和(不包括最后修改时间和最后访问时间),如果校验和不一致,则不会使用缓存。
注意
除了这两个命令,docker并不会去检查容器内的文件内容,比如RUN apt-get -y update,每次执行时文件可能都不一样,但是docker认为命令一致,会继续使用缓存。这样一来,以后构建时都不会再重新运行apt-get -y update。
如果docker没有找到当前指令的缓存,则会构建一个新的镜像,并且之后的所有指令都不会再去寻找缓存。
1.3 .dockerignore
在docker构建镜像的第一步,docker CLI会先在上下文目录中寻找.dockerignore文件,根据.dockerignore文件排除上下文目录中的部分文件和目录,,然后把剩下的文件和目录传递给docker 服务。.dockerignore语法同.gitignore,具体不表,可以参考官方文档。
二、Dockerfile其他说明
2.1 格式
虽然Dockerfile并不区分大小写,但还是约定指令使用大写。
Dockerfile的第一条可执行指令必须是FROM。
以#开头的是注释,行内的#都被当做参数,并且不支持续行。
2.2 解析指令(parser directives)
解析指令也以#开头,形式如下:
# directive=value1
# directive=value2 FROM ImageName
解析指令是可选的,虽然不区分大小写,但还是约定使用小写。
解析指令会影响到Dockerfile的解析逻辑,并且不会生成图层,也不会在构建时显示。解析指令只能出现在Dockerfile头部,并且一条解析指令只能出现一次。如果碰到注释、Dockerfile指令或空行,接下来出现的解析指令都无效,被当做注释处理。不支持续行。
根据文档当前只有一个解析指令:escape。(我猜如果有必要可能会在之后的版本中增加新的解析指令吧)
escape用来设置转义或续行字符,这在Windows中很有用:
COPY testfile.txt c:\\
RUN dir c:\
会被docker解析成: COPY teestfile.txt c:\RUN dir c:,下面的例子就可以正常执行。
# escape=`
COPY testfile.txt c:\
RUN dir c:\
2.3 Dockerfile中的环境变量
在Dockerfile中,使用env指令来定义环境变量。环境变量有两种形式:$variable_name和${variable_name},推荐使用后者,因为:
- 可以复合值,如
${foo}_bar,前者就无法做到 - 支持部分bash语法,其中
word除了字符串外也支持环境变量,进行递归替换${variable:-word}:如果variable不存在,则使用word${varialbe:+word}:如果variable存在,则使用word,如果variable不存在,则使用空字符串
支持这些指令:ADD,COPY,ENV,EXPOSE,LABEL,USER,WORKDIR,VOLUME,STOPSIGNAL和1.4版本之后的ONBUILD
注意
在整个指令行中只使用一个值,参考下面这个例子:
ENV abc=hello
ENV abc=bye def=$abc
ENV ghi=$abc
最后abc=bye, def=hello, ghi=bye
三、指令集
几个注意点:
- 大部分同时有shell格式和exec格式的指令,他们的区别在于:
- shell格式的是在某个shell中(默认为
/bin/sh -c)运行可执行文件,exec格式的是直接执行可执行文件。如果exec格式的要在某个shell中执行,要这么写:["/bin/bash", "-c", "echo hello"]。shell可以使用SHELL指令来更改。 - shell格式支持使用转义符(默认为
\\)来换行。 - exec格式被解析为json数组,所以使用双引号
"而不是单引号'。 - exec格式因为不在shell中执行,不会进行变量替换,而shell格式的可以。如
RUN ["echo", "$HOME"]不会将$HOME展开,如果需要展开变量,可以这样使用:RUN ["sh", "-c", "echo $HOME"]
- shell格式的是在某个shell中(默认为
- 如果以
docker build - < somefile这种方式来构建镜像的,则没有上下文,ADD只能使用远程文件URL而COPY不能使用。如果以docker build - < archive.tar.gz,则会在压缩包的根目录中寻找Dockerfile,压缩包的根目录当做上下文。 - 如果远程文件是需要登录才能访问的,应该使用
RUN wget或RUN curl,而不是直接使用ADD或COPY。 - 使用
ADD或COPY远程文件的,会赋予文件600权限,并且HTTPLast-Modified时间就是文件的最后修改时间。最后修改时间被改变,docker不会认为文件被改变,docker只会检查文件内容。
3.1 FROM
构建的镜像继承自某个base image。格式:
FROM <image>
FROM <image>:<tag>
FROM <image>@<digest>
FROM指令必须是Dockerfile的第一个指令,可以使用多次来构建多个镜像,以最后一个镜像的ID为输出值。tag和digest是可选的,如果不提供则使用latest。
3.2 RUN
在镜像的构建过程中执行特定的命令,并生成一个中间镜像。格式:
RUN <command>:shell格式RUN ["executable", "param1", "param2"]:exec格式
3.3 CMD
指定容器运行时的默认参数,如果出现多次以最后一次为准。格式:
CMD ["executable", "param1", "param2"]:exec格式CMD command param1 param2:shell格式CMD ["param1", "param2"]:省略可执行文件的exec格式,这种写法使CMD中的参数当做ENTRYPOINT的默认参数,此时ENTRYPOINT也应该是exec格式
具体与ENTRYPOINT的组合使用,参考ENTRYPOINT。
注意
与RUN指令的区别:RUN在构建的时候执行,并生成一个新的镜像,CMD在容器运行的时候执行,在构建时不进行任何操作。
3.4 LABEL
给构建的镜像打标签。格式:
LABEL <key>=<value> <key>=<value> <key>=<value> ...
如果base image中也有标签,则继承,如果是同名标签,则覆盖。
为了减少图层数量,尽量将标签写在一个LABEL指令中去,如:
LABEL multi.label1="value1" \
multi.label2="value2" \
other="value3"
3.5 MAINTAINER(已被弃用)
为构建的镜像设置作者信息。格式:
MAINTAINER <name>
LABEL比MAINTAINER更灵活,推荐使用LABEL,弃用MAINTAINER。
3.6 EXPOSE
为构建的镜像设置监听端口,使容器在运行时监听。格式:
EXPOSE <port> [<port>...]
EXPOSE指令并不会让容器监听host的端口,如果需要,需要在docker run时使用-p、-P参数来发布容器端口到host的某个端口上。
3.7 ENV
在构建的镜像中设置环境变量,在后续的Dockerfile指令中可以直接使用,也可以固化在镜像里,在容器运行时仍然有效。格式:
ENV <key> <value>:把第一个空格之后的所有值都当做<key>的值,无法在一行内设定多个环境变量。ENV <key>=<value> ...:可以设置多个环境变量,如果<value>中存在空格,需要转义或用引号"括起来。
docker推荐使用第二种,因为可以在一行中写多个环境变量,减少图层。如下:
ENV myName="John Doe" \
myDog=Rex\ The\ Dog \
myCat=fluffy
注意
- 可以在容器运行时指定环境变量,替换镜像中的已有变量,
docker run --env <key>=<value>。 - 使用
ENV可能会对后续的Dockerfile指令造成影响,如果只需要对一条指令设置环境变量,可以使用这种方式:RUN <key>=<value> <command>
3.8 ADD
在构建镜像时,复制上下文中的文件到镜像内,格式:
ADD <src>... <dest>ADD ["<src>",... "<dest>"]
<src>可以是文件、目录,也可以是文件URL。可以使用模糊匹配(wildcards,类似shell的匹配),可以指定多个<src>,必须是在上下文目录和子目录中,无法添加../a.txt这样的文件。如果<src>是个目录,则复制的是目录下的所有内容,但不包括该目录。如果<src>是个可被docker识别的压缩包,docker会以tar -x的方式解压后将内容复制到<desct>。<dest>可以是绝对路径,也可以是相对WORKDIR目录的相对路径。
所有文件的UID和GID都是0。
注意
如果docker发现文件内容被改变,则接下来的指令都不会再使用缓存。
关于复制文件时需要处理的/,基本跟正常的copy一致,具体参考ADD指令。
3.9 COPY
与ADD类似,只不过ADD是将上下文内的文件复制到镜像内,COPY是在镜像内的复制。格式与ADD一致。
注意
如果<dest>不存在,COPY指令会自动创建所有目录,包括子目录
3.10 ENTRYPOINT
指定镜像的执行程序,只有最后一条ENTRYPOINT指令有效。格式:
ENTRYPOINT <command> <param1> <param2>:shell格式,因为嵌套在shell中,PID不再为1,也接受不到Unix信号,即在docker stop <container>时收不到SIGTERM信号,需要手动写脚本使用exec或gosu命令处理。ENTRYPOINT ["<executable>", "<param1>", "<param2>"]:exec格式,PID为1
官方文档有两个例子:Exec form ENTRYPOINT example和Shell form ENTRYPOINT example。
CMD和ENTRYPOINT至少得使用一个。ENTRYPOINT应该被当做docker的可执行程序,CMD应该被当做ENTRYPOINT的默认参数。docker run <image> <arg1> <arg2> ...会把之后的参数传递给ENTRYPOINT,覆盖CMD指定的参数。可以用docker run --entrypoint来重置默认的ENTRYPOINT。
关于ENTRYPOINT和CMD的交互,用一个官方表格可以说明:
| No ENTRYPOINT | ENTRYPOINT exec_entry p1_entry | ENTRYPOINT ["exec_entry", "p1_entry"] | |
|---|---|---|---|
| No CMD | error, not allowed | /bin/sh -c exec_entry p1_entry | exec_entry p1_entry |
| CMD ["exec_cmd", "p1_cmd"] | exec_cmd p1_cmd | /bin/sh -c exec_entry p1_entry | exec_entry p1_entry exec_cmd p1_cmd |
| CMD ["p1_cmd", "p2_cmd"] | p1_cmd p2_cmd | /bin/sh -c exec_entry p1_entry | exec_entry p1_entry p1_cmd p2_cmd |
| CMD exec_cmd p1_cmd | CMD exec_cmd p1_cmd | /bin/sh -c exec_entry p1_entry | exec_entry p1_entry /bin/sh -c exec_cmd p1_cmd |
3.11 VOLUME
指定镜像内的目录为数据卷。格式:
VOLUME ["/var/log"]
VOLUME /var/log /var/db
在容器运行的时候,docker会把镜像中的数据卷的内容复制到容器的数据卷中去。
如果在接下来的Dockerfile指令中,修改了数据卷中的内容,则修改无效。
3.12 USER
为接下来的Dockerfile指令指定用户。格式:
USER daemon
收影响的指令有:RUN、CMD、ENTRYPOINT。
3.13 WORKDIR
为接下来的Dockerfile指令指定当前工作目录,可多次使用,如果使用的是相对路径,则相对的是上一个工作目录,类似shell中的cd命令。格式:
WORKDIR /path/to/workdir
收影响的指令有:RUN、CMD、ENTRYPOINT、COPY和ADD。
3.14 ARG
指定了用户在docker build --build-arg <varname>=<value>时可以使用的参数。格式:
ARG <name>[=<default value>]
构建参数在定义的时候生效而不是在使用的时候。如下面第三行开始的user才是用户构建参数传递过来的user:
FROM busybox
USER ${user:-some_user}
ARG user
USER $user
后续的ENV指令会覆盖同名的构建参数,正常用法如下:
FROM ubuntu
ARG CONT_IMG_VER
ENV CONT_IMG_VER ${CONT_IMG_VER:-v1.0.0}
RUN echo $CONT_IMG_VER
docker内置了一批构建参数,可以不用在Dockerfile中声明:HTTP_PROXY、http_proxy、HTTPS_PROXY、https_proxy、FTP_PROXY、ftp_proxy、NO_PROXY、no_proxy
注意
在使用构建参数(而不是在构建参数定义的时候)的指令中,如果构建参数的值发生了变化,会导致该指令发生变化,会重新寻找缓存。
3.15 ONBUILD
向镜像中添加一个触发器,当以该镜像为base image再次构建新的镜像时,会触发执行其中的指令。格式:
ONBUILD [INSTRUCTION]
比如我们生成的镜像是用来部署Python代码的,但是因为有多个项目可能会复用该镜像。所以一个合适的方式是:
[...]
# 在下一次以此镜像为base image的构建中,执行ADD . /app/src,将项目代目添加到新镜像中去
ONBUILD ADD . /app/src
# 并且build Python代码
ONBUILD RUN /usr/local/bin/python-build --dir /app/src
[...]
注意ONBUILD只会继承给子节点的镜像,不会再继承给孙子节点。ONBUILD ONBUILD或者ONBUILD FROM或者ONBUILD MAINTAINER是不允许的。
3.16 STOPSIGNAL
触发系统信号。格式:
STOPSIGNAL signal
3.17 HEALTHCHECK
增加自定义的心跳检测功能,多次使用只有最后一次有效。格式:
HEALTHCHECK [OPTION] CMD <command>:通过在容器内运行command来检查心跳HEALTHCHECK NONE:取消从base image继承来的心跳检测
可选的OPTION:
--interval=DURATION:检测间隔,默认30秒--timeout=DURATION:命令超时时间,默认30秒--retries=N:连续N次失败后标记为不健康,默认3次
<command>可以是shell脚本,也可以是exec格式的json数组。
docker以<command>的退出状态码来区分容器是否健康,这一点同shell一致:
- 0:命令返回成功,容器健康
- 1:命令返回失败,容器不健康
- 2:保留状态码,不要使用
举例:每5分钟检测本地网页是否可访问,超时设为3秒:
HEALTHCHECK --interval=5m --timeout=3s \
CMD curl -f http://localhost/ || exit 1
可以使用docker inspect命令来查看健康状态。
注意
docker版本1.12
3.18 SHELL
更改后续的Dockerfile指令中所使用的shell。默认的shell是["bin/sh", "-c"]。可多次使用,每次都只改变后续指令。格式:
SHELL ["executable", "parameters"]
注意
docker版本1.12
深入Dockerfile(一): 语法指南(转)的更多相关文章
- Markdown编辑器语法指南2
人的一切痛苦, 本质上都是对自己的无能的愤怒. --王小波 1 Markdown编辑器的基本用法 1.1 代码 如果你只想高亮语句中的某个函数名或关键字,可以使用 `function_name()` ...
- Markdown语法指南
1.背景 个人比较喜欢用Markdonw写东西,比如写博客随笔,写有道云笔记等,但有的时候会突然忘记某个具体语法怎么写了,如插入图片.插入链接.表格等,那干脆把这个语法简单地总结一下,也方便日后快速查 ...
- Protobuf3 语法指南
目录 [−] 定义一个消息类型 指定字段类型 分配标识号 指定字段规则 添加更多消息类型 添加注释 保留标识符(Reserved) 从.proto文件生成了什么? 标量数值类型 默认值 枚举 使用其他 ...
- protobuf语法指南
遇到proto编译问问,看看proto语法,记录一下 protobuf3 语法指南 http://colobu.com/2017/03/16/Protobuf3-language-guide/ htt ...
- dockerfile文件语法命令
dockerfile文件语法命令 (1) FROM命令,支持两种形式,构建新镜像使用的基础镜像,所以源镜像必须存在,并且是非注释的第一条命令. DOCKERFILEFORM <image> ...
- gRPC-Protocol语法指南
语法指南 (proto3) Defining A Message Type Scalar Value Types Default Values Enumerations Using Other Mes ...
- ProtoBuf3语法指南(Protocol Buffers)_下
0.说明 ProtoBuf3语法指南, 又称为proto3, 是谷歌的Protocol Buffers第3个版本. 本文基于官方英文版本翻译, 加上了自己的理解少量修改, 一共分为上下两部分. 1.A ...
- ProtoBuf3语法指南(Protocol Buffers)_上
0.说明 ProtoBuf3语法指南, 又称为proto3, 是谷歌的Protocol Buffers第3个版本. 本文基于官方英文版本翻译, 加上了自己的理解少量修改, 一共分为上下两部分. 1.序 ...
- Dockerfile编写语法
docker镜像本质上就是一个个基础镜像的堆叠,为了做出我们想要的镜像,我们需要考虑最终镜像所需的所有基础环境,然后一层层堆叠.也就是不断以基础镜像搭建上层镜像. 先看例子: # Version: # ...
随机推荐
- consul分布式集群搭建
环境准备 三台机器: vm-a 10.200.110.90 centos7vm-b 10.200.110.91 centos7vm-c 10.200.110.93 ...
- javaAgent介绍
JavaAgent(转载) http://www.cnblogs.com/diyunpeng/archive/2011/05/26/2057932.html 一文带你了解Java Agent http ...
- MFC 单文档调用对话框
1.插入新的Dialog,如下图: 2.修改ID位 IDD_XMB 3.在单文件的Menu 中选中需要链接的按键,右键添加处理程序,如下图所示,添加完成后,在项目的xxxview.cpp中会生成如下函 ...
- openj9
下面部分转自:https://www.jianshu.com/p/916b5fcd0140 OpenJ9,OMR与OpenJDK Eclipse OpenJ9 是一个 Java 虚拟机(JVM) ...
- python之路——17
王二学习python的笔记以及记录,如有雷同,那也没事,欢迎交流,wx:wyb199594 复习 1.迭代器2.生成器3.内置函数 1.学习55个 2.带key的,max min filter map ...
- 为什么我说IPFS社区从卖矿机开始,就是错的
要回答这个问题,首先要了解去中心化存储项目和传统的区块链项目有什么区别.其中去中心化存储项目包括IPFS,基于IPFS的FileCoin.PPIO.Storj等. 传统区块链项目没有供需问题 首先以比 ...
- 如何限制指定textFiled第三方输入法切换
在有些项目中需要用到输入纯数字的键盘,并且还不能切换到第三方输入法! textFiled.secureTextEntry = YES; [textFiled addTarget:self action ...
- homestead安装
所谓Homestead,其实就是一个虚拟机镜像. 为什么用它?它的优点可以去自行百度.(虽然我还是用了集成环境 xampp,但是我还是不推荐的,特别是统一开发环境.或者去使用诸如Laravel的框架, ...
- C语言排序算法学习笔记——交换类排序
交换类排序:根据序列中两个元素关键字的比较结果来交换他俩在序列中的位置. 冒泡排序:假设待排序表长为n,从后往前(或从前往后)两两比较相邻元素的值,若为逆序(即A[i-1]>A[i])则交换他们 ...
- [STM32F103]定时器PWM输入
typedef struct { uint16_t TIM_OCMode; //PWM模式1或者模式2 uint16_t TIM_OutputState; //输出使能 OR失能 uint16_t ...