Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

深入Dockerfile(一): 语法指南 #35

Open
qianlei90 opened this issue Mar 9, 2017 · 3 comments
Open

深入Dockerfile(一): 语法指南 #35

qianlei90 opened this issue Mar 9, 2017 · 3 comments

Comments

@qianlei90
Copy link
Owner

@qianlei90 qianlei90 commented Mar 9, 2017

深入Dockerfile(一): 语法指南

Tags: 印象笔记 Docker

docker官方文档Dockerfile reference的笔记。

[toc]


一、机制

1.1 构建

docker构建一个镜像,需要:

  1. Dockerfile文件
  2. 构建所需的上下文
$ docker build .

这条命令中,docker CLI会:

  1. 把当前目录及子目录当做上下文传递给docker服务
  2. 从当前目录(不包括子目录)中找到Dockerfile
  3. 检查Dockerfile的语法
  4. 依次执行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|
+-------+     +-------+

大部分指令可以根据上述逻辑去寻找缓存,除了ADDCOPY
这两个指令会复制文件内容到镜像内,除了指令相同以外,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},推荐使用后者,因为:

  1. 可以复合值,如${foo}_bar,前者就无法做到
  2. 支持部分bash语法,其中word除了字符串外也支持环境变量,进行递归替换
    • ${variable:-word}:如果variable不存在,则使用word
    • ${varialbe:+word}:如果variable存在,则使用word,如果variable不存在,则使用空字符串

支持这些指令:ADDCOPYENVEXPOSELABELUSERWORKDIRVOLUMESTOPSIGNAL和1.4版本之后的ONBUILD

注意
在整个指令行中只使用一个值,参考下面这个例子:

ENV abc=hello
ENV abc=bye def=$abc
ENV ghi=$abc

最后abc=bye, def=hello, ghi=bye

三、指令集

几个注意点:

  • 大部分同时有shell格式和exec格式的指令,他们的区别在于:
    1. shell格式的是在某个shell中(默认为/bin/sh -c)运行可执行文件,exec格式的是直接执行可执行文件。如果exec格式的要在某个shell中执行,要这么写:["/bin/bash", "-c", "echo hello"]。shell可以使用SHELL指令来更改。
    2. shell格式支持使用转义符(默认为\\)来换行。
    3. exec格式被解析为json数组,所以使用双引号"而不是单引号'
    4. exec格式因为不在shell中执行,不会进行变量替换,而shell格式的可以。如RUN ["echo", "$HOME"]不会将$HOME展开,如果需要展开变量,可以这样使用:RUN ["sh", "-c", "echo $HOME"]
  • 如果以docker build - < somefile这种方式来构建镜像的,则没有上下文,ADD只能使用远程文件URL而COPY不能使用。如果以docker build - < archive.tar.gz,则会在压缩包的根目录中寻找Dockerfile,压缩包的根目录当做上下文。
  • 如果远程文件是需要登录才能访问的,应该使用RUN wgetRUN curl,而不是直接使用ADDCOPY
  • 使用ADDCOPY远程文件的,会赋予文件600权限,并且HTTP Last-Modified时间就是文件的最后修改时间。最后修改时间被改变,docker不会认为文件被改变,docker只会检查文件内容。

3.1 FROM

构建的镜像继承自某个base image。格式:

FROM <image>
FROM <image>:<tag>
FROM <image>@<digest>

FROM指令必须是Dockerfile的第一个指令,可以使用多次来构建多个镜像,以最后一个镜像的ID为输出值。
tagdigest是可选的,如果不提供则使用latest

3.2 RUN

在镜像的构建过程中执行特定的命令,并生成一个中间镜像。格式:

  1. RUN <command>:shell格式
  2. RUN ["executable", "param1", "param2"]:exec格式

3.3 CMD

指定容器运行时的默认参数,如果出现多次以最后一次为准。格式:

  1. CMD ["executable", "param1", "param2"]:exec格式
  2. CMD command param1 param2:shell格式
  3. 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>

LABELMAINTAINER更灵活,推荐使用LABEL,弃用MAINTAINER

3.6 EXPOSE

为构建的镜像设置监听端口,使容器在运行时监听。格式:

EXPOSE <port> [<port>...]

EXPOSE指令并不会让容器监听host的端口,如果需要,需要在docker run时使用-p-P参数来发布容器端口到host的某个端口上。

3.7 ENV

在构建的镜像中设置环境变量,在后续的Dockerfile指令中可以直接使用,也可以固化在镜像里,在容器运行时仍然有效。格式:

  1. ENV <key> <value>:把第一个空格之后的所有值都当做<key>的值,无法在一行内设定多个环境变量。
  2. ENV <key>=<value> ...:可以设置多个环境变量,如果<value>中存在空格,需要转义或用引号"括起来。

docker推荐使用第二种,因为可以在一行中写多个环境变量,减少图层。如下:

ENV myName="John Doe" \
    myDog=Rex\ The\ Dog \
    myCat=fluffy

注意

  1. 可以在容器运行时指定环境变量,替换镜像中的已有变量,docker run --env <key>=<value>
  2. 使用ENV可能会对后续的Dockerfile指令造成影响,如果只需要对一条指令设置环境变量,可以使用这种方式:RUN <key>=<value> <command>

3.8 ADD

在构建镜像时,复制上下文中的文件到镜像内,格式:

  1. ADD <src>... <dest>
  2. 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指令有效。格式:

  1. ENTRYPOINT <command> <param1> <param2>:shell格式,因为嵌套在shell中,PID不再为1,也接受不到Unix信号,即在docker stop <container>时收不到SIGTERM信号,需要手动写脚本使用exec或gosu命令处理。
  2. ENTRYPOINT ["<executable>", "<param1>", "<param2>"]:exec格式,PID为1

官方文档有两个例子:Exec form ENTRYPOINT exampleShell form ENTRYPOINT example

CMDENTRYPOINT至少得使用一个。ENTRYPOINT应该被当做docker的可执行程序,CMD应该被当做ENTRYPOINT的默认参数。
docker run <image> <arg1> <arg2> ...会把之后的参数传递给ENTRYPOINT,覆盖CMD指定的参数。可以用docker run --entrypoint来重置默认的ENTRYPOINT

关于ENTRYPOINTCMD的交互,用一个官方表格可以说明:

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

收影响的指令有:RUNCMDENTRYPOINT

3.13 WORKDIR

为接下来的Dockerfile指令指定当前工作目录,可多次使用,如果使用的是相对路径,则相对的是上一个工作目录,类似shell中的cd命令。格式:

WORKDIR /path/to/workdir

收影响的指令有:RUNCMDENTRYPOINTCOPYADD

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_PROXYhttp_proxyHTTPS_PROXYhttps_proxyFTP_PROXYftp_proxyNO_PROXYno_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

增加自定义的心跳检测功能,多次使用只有最后一次有效。格式:

  1. HEALTHCHECK [OPTION] CMD <command>:通过在容器内运行command来检查心跳
  2. 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

-\ 完 -\ 2017/03/09

@qianlei90 qianlei90 changed the title Dockerfile语法指南 深入Dockerfile(一): 语法指南 Mar 13, 2017
@qianlei90 qianlei90 changed the title 深入Dockerfile(一): 语法指南 深入Dockerfile(一): 语法指南 Mar 13, 2017
@nsdont

This comment has been minimized.

Copy link

@nsdont nsdont commented Sep 22, 2017

docker build - < Dockerfile

这里的 - 是代表什么?
我知道 docker build . 是使用当前目录的 Dockerfile

@qianlei90

This comment has been minimized.

Copy link
Owner Author

@qianlei90 qianlei90 commented Sep 22, 2017

@nsdont

这个-是docker build 命令中的一部分,表示Dockerfile的内容从标准输入中获取。< Dockerfile表示将Dockerfile中的文件内容重定向到标准输入。

$ docker build --help

Usage:  docker build [OPTIONS] PATH | URL | -

Build an image from a Dockerfile

...
@nsdont

This comment has been minimized.

Copy link

@nsdont nsdont commented Sep 23, 2017

原来如此,谢谢

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
2 participants
You can’t perform that action at this time.