Dockerfile参考(18) – SHELL设置执行命令的shell

格式:

  1. SHELL ["executable", "parameters"]

SHELL指令可以覆盖命令的shell模式所使用的默认shell。Linux的默认shell是[“/bin/sh”, “-c”],Windows的是[“cmd”, “/S”, “/C”]。SHELL指令必须以JSON格式编写。
SHELL指令在有两个常用的且不太相同的本地shell:cmd和powershell,以及可选的sh的windows上特别有用。
SHELL指令可以出现多次。每个SHELL指令覆盖之前的SHELL指令设置的shell,并影响随便的指令。例如:

  1. FROM windowsservercore
  2.  
  3. # Executed as cmd /S /C echo default
  4. RUN echo default
  5.  
  6. # Executed as cmd /S /C powershell -command Write-Host default
  7. RUN powershell -command Write-Host default
  8.  
  9. # Executed as powershell -command Write-Host hello
  10. SHELL ["powershell", "-command"]
  11. RUN Write-Host hello
  12.  
  13. # Executed as cmd /S /C echo hello
  14. SHELL ["cmd", "/S"", "/C"]
  15. RUN echo hello

当RUN, CMD和ENTRYPOINT使用shell形式时,将使用SHELL指令设置的shell执行。
以下的示例是windows常见的模式,可以使用SHELL指令精简:

  1. RUN powershell -command Execute-MyCmdlet -param1 "c:foo.txt"

由docker解析会是:

  1. cmd /S /C powershell -command Execute-MyCmdlet -param1 "c:foo.txt"

这个命令之所以低效有两个原因。首先,没有必须调用cmd.exe。第二,每个使用shell模式的RUN指令需要一个额外的powershell。
为了优化这个命令更有效率,有两个方法,其中之一使用RUN指令的JSON形式,如:

  1. RUN ["powershell", "-command", "Execute-MyCmdlet", "-param1 "c:\foo.txt""]

虽然没有调用了cmd.exe,不过需要对双引号进行转义,看起来比较冗长。另一种机制是使用SHELL指令和shell形式,使得windows用户可以使用更自然的语法,特别是与escape指令一起用时:

  1. # escape=`
  2.  
  3. FROM windowsservercore
  4. SHELL ["powershell","-command"]
  5. RUN New-Item -ItemType Directory C:Example
  6. ADD Execute-MyCmdlet.ps1 c:example
  7. RUN c:exampleExecute-MyCmdlet -sample ‘hello world’

构建结果为:

  1. PS E:dockerbuildshell> docker build -t shell .
  2. Sending build context to Docker daemon 3.584 kB
  3. Step 1 : FROM windowsservercore
  4.  —> 5bc36a335344
  5. Step 2 : SHELL powershell -command
  6.  —> Running in 87d7a64c9751
  7.  —> 4327358436c1
  8. Removing intermediate container 87d7a64c9751
  9. Step 3 : RUN New-Item -ItemType Directory C:Example
  10.  —> Running in 3e6ba16b8df9
  11.  
  12.  
  13.     Directory: C:
  14.  
  15.  
  16. Mode                LastWriteTime         Length Name
  17. —-                ————-         —— —-
  18. d—–         6/2/2016   2:59 PM                Example
  19.  
  20.  
  21.  —> 1f1dfdcec085
  22. Removing intermediate container 3e6ba16b8df9
  23. Step 4 : ADD Execute-MyCmdlet.ps1 c:example
  24.  —> 6770b4c17f29
  25. Removing intermediate container b139e34291dc
  26. Step 5 : RUN c:exampleExecute-MyCmdlet -sample ‘hello world’
  27.  —> Running in abdcf50dfd1f
  28. Hello from Execute-MyCmdlet.ps1 – passed hello world
  29.  —> ba0e25255fda
  30. Removing intermediate container abdcf50dfd1f
  31. Successfully built ba0e25255fda
  32. PS E:dockerbuildshell>

Docker 1.12开始添加了此SHELL功能。

Dockerfile参考(17) – HEALTHCHECK检查容器是否正常工作

HEALTHCHECK指令有两种形式:

  • HEALTHCHECK [OPTIONS] CMD command [通过在容器内运行一个命令来检查容器健康情况]
  • HEALTHCHECK NONE [禁用从base镜像继承的任何healthcheck]
  • HEALTHCHECK指令告诉Docker如何测试一个容器来检查它是否工作正常。这个可以用来检测如web server陷入了死循环且已经无法处理新的连接了,即使server进程仍然在运行。
    当一个容器设置了healthcheck之后,除了正常的状态,它多了一个health状态。这个状态初始为starting。当健康检查通过后,它变成了healthy(不管之前是什么状态)。当连续出现几次失败后,就变成unhealthy。
    在CMD之前的选项有:

  • –interval=DURATION [默认30s]
  • –timeout=DURATION [默认30s]
  • –retries=N [默认3]
  • 当容器启动之后,首先等待interval秒然后进行健康检查,然后等这次检查完成后再等interval秒之后继续再次检查,如此循环。
    如果有一个检查所花的时间超过了timeout秒,那么就认为这次检查失败了。
    如果连续retries次失败,就认为此容器状态为unhealthy。
    Dockerfile中只能使用一个HEALTHCHECK指令,如果设置了多次,就取最后一个。
    这个命令的CMD之后的命令可以是一个shell命令[如HEALTHCHECK CMD /bin/check-running]或者exec数组[像Dockerfile命令ENTRYPOINT使用的数组]。
    命令退出状态表示容器的健康状态。可能的值为:

  • 0: success – 容器是健康的。
  • 1: unhealthy – 容器工作不正常。
  • 2: reserved – 不要使用这个退出代码
  • 例如,隔5分钟检查一次容器确保web server能够在3秒内正常输出主页:

    1. HEALTHCHECK –interval=5m –timeout=3s
    2.   CMD curl -f http://localhost/ || exit 1

    为了方便调查失败原因,检查命令的输出(UTF-8编码)会写到health状态中,并且可以通过docker inspect查看。输出应该尽量短,不大于4096字节。
    HEALTHCHECK功能是在Docker 1.12添加的。

    Dockerfile参考(16) – ONBUILD向镜像添加触发指令

    格式:

    1. ONBUILD [INSTRUCTION]

    ONBUILD指令向镜像添加稍后要执行的触发指令,该触发指令在该镜像作为另一个镜像构建的base镜像时执行。触发指令在另一个镜像构建的Dockerfile的FROM指令后马上执行,就像FROM指令后插入触发指令一样。
    任何的构建指令都可以注册为触发指令。
    如果你正在构建的镜像会作为构建其它镜像的base镜像时,ONBUILD会有用,例如一个应用程序的构建环境或者可能需要用户自定义配置的daemon。
    例如,如果你的镜像是一个可重复使用的Python应用程序镜像,你将需要把应用程序源码添加到指定的目录,和有可能需要在添加代码之后执行脚本启动应用程序。你不能仅仅的在base镜像中调用ADD和RUN,因为这时还没有应用程序源代码,并且每个应用程序配置都可能不一样。你可以简单地为应用程序开发者提供一个样本Dockerfile来复制粘贴到它们的应用程序,不过这个比较低效,容易出错和难以更新,因为它与应用程序特定代码混合了。
    解决方案是使用ONBUIL来注册一个在下一个镜像构建时执行的高级指令。
    下面是它的工作原理:
    1.当镜像构建程序遇到ONBUILD指令时,构建程序把这个触发器添加到正在构建的镜像元数据中。这个ONBUILD指令不会影响到当前镜像的构建过程。
    2.在完成构建镜像后,在关键词OnBuild下,所有的触发器明显地存储到了镜像。之后也可以使用docker inspect来查看它们。
    3.之后其它镜像构建可以会用到上面的镜像作为base镜像,这个可以直接使用FROM引入base镜像。在构建程序执行Dockerfile中的FROM指令时,其中部分处理过程查找ONBUILD触发器,并按原来注册的顺序来执行触发指令。如果任何的一个触发器失败了,FROM指令将中断,反过来导致构建失败。如果所有的触发器指令执行成功,FROM指令就执行完成了,构建继教执行FROM后的指令。
    4.构建新镜像完成时会清除触发器,也就是使用带触发器镜像作为base镜像构建新镜像不会继承它的触发器。
    例如base镜像的Dockerfile如下,镜像名称为baseimg:

    1. […]
    2. ONBUILD ADD . /app/src
    3. ONBUILD RUN /usr/local/bin/python-build –dir /app/src
    4. […]

    然后下面是构建新镜像的Dockerfile,镜像名称newimg:

    1. FROM baseimg
    2. […]

    当构建newimg镜像时,在构建程序执行FROM baseimg指令时,会依次执行baseimg注册的触发指令ADD . /app/src和RUN /usr/local/bin/python-build –dir /app/src,相当于在FROM baseimg指令后面自动添加了这两个指令。

    Dockerfile参考(15) – ARG指令定义由用户在命令行赋值的变量

    格式:

    1. ARG <name>[=<default value>]

    ARG指令定义了一个变量,能让用户可以在构建期间使用docker build命令和其参数–build-arg =对这个变量赋值。如果用户指定了一个构建参数没有定义在Dockerfile的话,将输出错误。

    1. One or more build-args were not consumed, failing build.

    Dockerfile作者可以指定ARG一次定义一个变量,或者指定ARG多次定义多个变量。例如:

    1. FROM busybox
    2. ARG user1
    3. ARG buildno

    Dockerfile作者也可以为这个变量指定一个默认值:

    1. FROM busybox
    2. ARG user1=someuser
    3. ARG buildno=1

    如果ARG指定了一个默认值并且在构建期间没有传递值过去,那么就使用默认值。
    ARG变量定义从在Dockerfile定义的行生效,而不是从在命令行参数的使用或其它地方。
    例如下面的Dockerfile:

    1. 1 FROM busybox
    2. 2 USER ${user:-some_user}
    3. 3 ARG user
    4. 4 USER $user

    使用如下命令构建:

    1. $ docker build –build-arg user=what_user Dockerfile

    第2行的USER的值为some_user,因为user变量在第3行才定义。第4行的USER值为what_user,因为user变量在它之前定义了并且在命令行给user变量赋值为what_user。在ARG指令定义变量之前引用这个变量的得,都会得到空值。

    警告:不推荐在构建期间的命令行传递密码如github密钥,用户凭证等数据。构建期间设置的变量可以通过docker history命令来查看。

    可以使用ARG或ENV指令指定可用于RUN指令的变量。使用ENV定义的环境变量始终会覆盖同一名称的ARG指令定义的变量。例如:

    1. 1 FROM ubuntu
    2. 2 ARG CONT_IMG_VER
    3. 3 ENV CONT_IMG_VER v1.0.0
    4. 4 RUN echo $CONT_IMG_VER

    然后使用如下命令构建镜像:

    1. $ docker build –build-arg CONT_IMG_VER=v2.0.1 Dockerfile

    在这种情况中,RUN指令解析CONT_IMG_VER变量的值为v1.0.0而不是ARG设置并由用户传递过来的v2.0.1。
    使用上面的示例,但不一样的ENV定义可以在ARG和ENV指令之前创建更有用的交互:

    1. 1 FROM ubuntu
    2. 2 ARG CONT_IMG_VER
    3. 3 ENV CONT_IMG_VER ${CONT_IMG_VER:-v1.0.0}
    4. 4 RUN echo $CONT_IMG_VER

    不像ARG指令,ENV的值始终会存在于镜像中。使用如下不带–build-arg构建镜像:

    1. $ docker build Dockerfile

    使用这个Dockerfile示例,CONT_IMG_VER仍然会存在于镜像中,不过它的值会是默认的v1.0.0,当然你也可以在命令行中更改它。
    Docker有一组预设置的ARG变量,你不需要在Dockerfile中定义就能够使用它。

  • HTTP_PROXY
  • http_proxy
  • HTTPS_PROXY
  • https_proxy
  • FTP_PROXY
  • ftp_proxy
  • NO_PROXY
  • no_proxy
  • 要设置这些变量,可以在命令行赋值

    1. –build-arg <varname>=<value>

    ARG对构建缓存的影响

    ARG变量不像ENV变量始终存在于镜像中。不过ARG变量会以类似的方式对构建缓存产生影响。如果Dockerfile中定义的ARG变量的值与之前定义的变量值不一样,那么就有可能产生“cache miss”。比如RUN指令使用ARG定义的变量时,ARG变量的值变了之后,就会导致缓存失效。

    Dockerfile参考(14) – WORKDIR设置RUN CMD ENTRYPOINT等指令的工作目录

    格式:

    1. WORKDIR /path/to/workdir

    WORKDIR指令设置Dockerfile中的任何RUN, CMD, ENTRYPOINT, COPY和ADD指令的工作目录。如果WORKDIR指定的目录不存在,即使随后的指令没有用到这个目录,都会创建。
    单个Dockerfile可以使用多次WORKDIR。如果提供一个相对路径,当前的工作目录将与上个WORKDIR指令相关,如:

    1. WORKDIR /a
    2. WORKDIR b
    3. WORKDIR c
    4. RUN pwd

    pwd命令的输出为/a/b/c。
    WORKDIR可以解析之前由ENV设置的环境变量。如:

    1. ENV DIRPATH /path
    2. WORKDIR $DIRPATH/$DIRNAME
    3. RUN pwd

    pwd命令输出为/path/$DIRNAME。

    Dockerfile参考(13) – VOLUME创建数据卷和USER指定容器内运行命令的用户

    VOLUME

    1. VOLUME ["/data"]

    VOLUME指令创建一个指定名称的挂载点,并设置此挂载点为本地主机或其它容器的外部挂载数据卷。VOLUME的值可以是一个JSON数组,如VOLUME [“/var/log/”],或者多个参数的纯字符串,如VOLUME /var/log或VOLUME /var/log /var/db。
    docker run把在base镜像内指定路径已存在的数据复制到这个新创建的数据卷。例如:

    1. FROM ubuntu
    2. RUN mkdir /myvol
    3. RUN echo "hello world" > /myvol/greeting
    4. VOLUME /myvol

    这个Dockerfile使得docker run创建一个新的挂载点/myvol,并复制greeting文件到新创建的数据卷。

    注意:如果在volume声明之后的任何指令对其挂载点更改了数据,这些更改将会撤消。

    USER

    1. USER daemon

    USER指令设置运行镜像时使用的用户名或UID,以及Dockerfile中的任何RUN,CMD和ENTRYPOINT指令。

    Dockerfile参考(12) – ENTRYPOINT配置创建容器后执行的容器命令

    ENTRYPOINT有两种形式:

  • ENTRYPOINT [“executable”, “param1”, “param2”] [exec形式,首选]
  • ENTRYPOINT command param1 param2 [shell形式]
  • ENTRYPOINT指令用来配置创建容器时执行的容器命令。
    例如,下面的示例将以默认页启动nginx,在80端口监听:

    1. docker run -i -t –rm -p 80:80 nginx

    docker run [image]的命令行参数会附加到exec形式ENTRYPOINT所有元素的后面,且会覆盖使用CMD指定的所有元素。这允许命令行参数能够传递到entry point。如docker run [image] -d会传递-d参数到entry point。你可以你使用docker run –entrypoint标志来覆盖ENTRYPOINT指令。
    shell形式的ENTRYPOINT能够阻止使用任何CMD或run命令行参数,不过不好的地方是ENTRYPOINT将以/bin/sh -c的子命令执行,且ENTRYPOINT收不到信号。意味着执行的命令的PID不是容器的PID 1 – 收不到来自主机的Unix信号 – 所以无法使用docker stop [container]停止容器。
    仅是Dockerfile的最后一个ENTRYPOINT指令起作用。

    exec形式ENTRYPOINT示例

    你可以使用ENTRYPOINT的exec形式设置稳定的默认命令和参数,然后使用同样形式的CMD设置额外的可以修改的默认参数。

    1. FROM ubuntu
    2. ENTRYPOINT ["top", "-b"]
    3. CMD ["-c"]

    当你运行容器,你会看到top是唯一的进程:

    1. $ docker run -it –rm –name test  top -H
    2. top – 08:25:00 up  7:27,  0 users,  load average: 0.00, 0.01, 0.05
    3. Threads:   1 total,   1 running,   0 sleeping,   0 stopped,   0 zombie
    4. %Cpu(s):  0.1 us,  0.1 sy,  0.0 ni, 99.7 id,  0.0 wa,  0.0 hi,  0.0 si,  0.0 st
    5. KiB Mem:   2056668 total,  1616832 used,   439836 free,    99352 buffers
    6. KiB Swap:  1441840 total,        0 used,  1441840 free.  1324440 cached Mem
    7.  
    8.   PID USER      PR  NI    VIRT    RES    SHR S %CPU %MEM     TIME+ COMMAND
    9.     1 root      20   0   19744   2336   2080 R  0.0  0.1   0:00.04 top

    我们再看到top的具体参数:

    1. $ docker exec -it test ps aux
    2. USER       PID %CPU %MEM    VSZ   RSS TTY      STAT START   TIME COMMAND
    3. root         1  2.6  0.1  19752  2352 ?        Ss+  08:24   0:00 top -b -H
    4. root         7  0.0  0.1  15572  2164 ?        R+   08:25   0:00 ps aux

    并且可以使用docker stop test来停止这个容器。
    下面的Dockerfile显示了使用ENTRYPOINT来在前台运行Apache[作为PID 1运行]:

    1. FROM debian:stable
    2. RUN apt-get update && apt-get install -y –force-yes apache2
    3. EXPOSE 80 443
    4. VOLUME ["/var/www", "/var/log/apache2", "/etc/apache2"]
    5. ENTRYPOINT ["/usr/sbin/apache2ctl", "-D", "FOREGROUND"]

    如果你需要为单个可执行程序编辑一个启动脚本,你可以使用exec和gosu命令来确保最终的执行程序能收到Unix信号:

    1. #!/bin/bash
    2. set -e
    3.  
    4. if [ "$1" = ‘postgres’ ]; then
    5.     chown -R postgres "$PGDATA"
    6.  
    7.     if [ -z "$(ls -A "$PGDATA")" ]; then
    8.         gosu postgres initdb
    9.     fi
    10.  
    11.     exec gosu postgres "$@"
    12. fi
    13.  
    14. exec "$@"

    最后,如果你需要在关闭或协调多个可执行程序时做些额外的清理工作,你需要确保ENTRPOINT脚本能够收到Unix信号,捕获它们并做些额外的工作:

    1. #!/bin/sh
    2. # Note: I’ve written this using sh so it works in the busybox container too
    3.  
    4. # USE the trap if you need to also do manual cleanup after the service is stopped,
    5. #     or need to start multiple services in the one container
    6. trap "echo TRAPed signal" HUP INT QUIT TERM
    7.  
    8. # start service in background here
    9. /usr/sbin/apachectl start
    10.  
    11. echo "[hit enter key to exit] or run ‘docker stop <container>’"
    12. read
    13.  
    14. # stop service and clean up here
    15. echo "stopping apache"
    16. /usr/sbin/apachectl stop
    17.  
    18. echo "exited $0"

    如果使用docker run -it –rm -p 80:80 –name test apache运行镜像,可以用docker exec或docker top来检查容器进程,然后请求脚本停止Apache:

    1. $ docker exec -it test ps aux
    2. USER       PID %CPU %MEM    VSZ   RSS TTY      STAT START   TIME COMMAND
    3. root         1  0.1  0.0   4448   692 ?        Ss+  00:42   0:00 /bin/sh /run.sh 123 cmd cmd2
    4. root        19  0.0  0.2  71304  4440 ?        Ss   00:42   0:00 /usr/sbin/apache2 -k start
    5. www-data    20  0.2  0.2 360468  6004 ?        Sl   00:42   0:00 /usr/sbin/apache2 -k start
    6. www-data    21  0.2  0.2 360468  6000 ?        Sl   00:42   0:00 /usr/sbin/apache2 -k start
    7. root        81  0.0  0.1  15572  2140 ?        R+   00:44   0:00 ps aux
    8. $ docker top test
    9. PID                 USER                COMMAND
    10. 10035               root                {run.sh} /bin/sh /run.sh 123 cmd cmd2
    11. 10054               root                /usr/sbin/apache2 -k start
    12. 10055               33                  /usr/sbin/apache2 -k start
    13. 10056               33                  /usr/sbin/apache2 -k start
    14. $ /usr/bin/time docker stop test
    15. test
    16. real    0m 0.27s
    17. user    0m 0.03s
    18. sys 0m 0.03s

    注意:你可以使用–entrypoint来覆盖ENTRPOINT的设置,不过仅可以二进制到exec,如/usr/sbin/apache2 -k start[不仅调用sh -c执行]

    shell形式ENTRYPOINT示例

    可以为ENTRYPOINT指定一个纯字符串,它将运行在/bin/sh -c。这种形式将使用shell处理shell环境变量替换,将忽略任何的CMD或docker run命令行参数。为了确保docker stop能够正确发送信号到ENTRPOINT的可执行程序,记得使用exec执行:

    1. FROM ubuntu
    2. ENTRYPOINT exec top -b

    运行这个镜像后,将会看到这个进程的PID为1:

    1. $ docker run -it –rm –name test top
    2. Mem: 1704520K used, 352148K free, 0K shrd, 0K buff, 140368121167873K cached
    3. CPU:   5% usr   0% sys   0% nic  94% idle   0% io   0% irq   0% sirq
    4. Load average: 0.08 0.03 0.05 2/98 6
    5.   PID  PPID USER     STAT   VSZ %VSZ %CPU COMMAND
    6.     1     0 root     R     3164   0%   0% top -b

    且可以使用docker stop停止进程:

    1. $ /usr/bin/time docker stop test
    2. test
    3. real    0m 0.20s
    4. user    0m 0.02s
    5. sys 0m 0.04s

    如果忘记使用exec执行命令:

    1. FROM ubuntu
    2. ENTRYPOINT top -b
    3. CMD –ignored-param1

    然后运行:

    1. $ docker run -it –name test top –ignored-param2
    2. Mem: 1704184K used, 352484K free, 0K shrd, 0K buff, 140621524238337K cached
    3. CPU:   9% usr   2% sys   0% nic  88% idle   0% io   0% irq   0% sirq
    4. Load average: 0.01 0.02 0.05 2/101 7
    5.   PID  PPID USER     STAT   VSZ %VSZ %CPU COMMAND
    6.     1     0 root     S     3168   0%   0% /bin/sh -c top -b cmd cmd2
    7.     7     1 root     R     3164   0%   0% top -b

    可以看到top进程的PID为是1了。
    并且使用docker stop test不能停止进程:

    1. $ docker exec -it test ps aux
    2. PID   USER     COMMAND
    3.     1 root     /bin/sh -c top -b cmd cmd2
    4.     7 root     top -b
    5.     8 root     ps aux
    6. $ /usr/bin/time docker stop test
    7. test
    8. real    0m 10.19s
    9. user    0m 0.04s
    10. sys 0m 0.03s

    ENTRYPOINT和CMD使用建议

    1.Dockerfile应该至少指定一个CMD或ENTRYPOINT指令。
    2.当使用容器作为可执行程序时,应该定义ENTRYPOINT。
    3.CMD应该用来设置ENTRYPOINT的默认参数。
    4.当运行容器带有额外参数时,将会覆盖CMD的参数。

    Dockerfile参考(11) – ADD COPY添加文件和目录到镜像

    ADD有两种形式:

  • ADD <src>… <dest>
  • ADD [“<src>”,… “<dest>”] [这种形式通常用于包含空白字符的路径]
  • ADD指令从<src>复制新的文件,目录或远程文件URLs并添加到镜像文件系统的<dest>路径。
    可以指定多个<src>资源,这些资源必须是相对于正在构建的源目录(构建的上下文)。
    每个<src>可以包含通配符并且使用Go的filepath.Match规则匹配。例如:

    1. ADD hom* /mydir/        # adds all files starting with "hom"
    2. ADD hom?.txt /mydir/    # ? is replaced with any single character, e.g., "home.txt"

    <dest>是一个绝对路径,或者是WORKDIR的相对路径。

    1. ADD test relativeDir/          # adds "test" to `WORKDIR`/relativeDir/
    2. ADD test /absoluteDir/         # adds "test" to /absoluteDir/

    所有新创建的文件和目录的UID和GID都是0.
    当<src>为一个远程文件URL时,新创建的文件和目录权限为600。如果获取的远程文件的HTTP有一个Last-Modified响应头,那么这个响应头的时间戳将设置为新创建的文件和目录的mtime。不过,在ADD期间像其它任意文件的处理过程中,mtime不能决定文件是否已经更改和缓存是否应该更新。

    注意:如果通过标准输入传递Dockerfile构建镜像[docker build – < somefile],那么就没有构建上下文,所以Dockerfile中的ADD指令的<src>只能是远程URL。你也可以通过标准输入传递一个压缩文件:(docker build – < archive.tar.gz),在这个压缩文件根目录的Dockerfile和压缩文件的其它文件将作为构建的上下文使用。

    如果远程URL需要验证,你需要使用RUN wget,RUN curl或容器内的其它工具下载文件,因为ADD指令不支持验证功能。

    如果<src>的文件已经更改,那么涉及到这个路径的第一个ADD指令将使Dockerfile后面的所有指令缓存失效。其中RUN指令的缓存也将失效。

    ADD遵守如下规则:

  • <src>路径必须在构建上下文内;你不能ADD ../something /something,因为docker build的第一步已经把上下文目录和子目录发送到docker daemon了。
  • 如果<src>是一个远程URL,且<dest>路径后没有跟斜杠,那么这个文件从远程URL下载并复制到<dest>
  • 如果<src>是一个远程URL,且<dest>路径后有斜杠,那么filename从URL获取并把文件下载到<dest>/<filename>。例如,ADD http://example.com/foobar /将创建文件/foobar。远程URL是包括路径的URL,这样docker才能获取文件名,像http://example.com是不行的。
  • 如果<src>是一个目录,目录的所有内容都将复制,包括文件系统元数据。
  • 注意:不会复制目录本身,只是它的内容。

  • 如果<src>是一个本地的可识别的tar压缩文件[如gzip,bzip2或xz],那么将在容器内解压为目录。远程URL的压缩文件将不会解压。
  • 如果<src>是多个资源,不管是直接指定或使用通配符,那么<dest>必须是一个目录,并且已经以斜杠/结尾。
  • 如果<dest>不以斜杠/结尾,将视其为一个普通文件,<src>的内容将写到这个文件。
  • 如果<dest>不存在,则会与其路径中的所有缺少的目录一起创建。
  • 注意:ADD与COPY唯一的区别就是COPY添加本地压缩文件时不会自动解压到容器。对于其它不需要自动解压文件的情况,你应该始终使用COPY。

    Dockerfile参考(10) – EXPOSE暴露端口和ENV设置环境变量

    EXPOSE

    1. EXPOSE <port> [<port>…]

    EXPOSE指令通知Docker,容器运行期间监听指定的网络端口。EXPOSE不会自动使主机的容器端口可访问。要设置监听EXPOSE指定的端口,必须使用-p参数来发布一组端口或-P参数来发布所有EXPOSE的端口。你可以暴露容器的一个端口并在外部发布另一个不同的端口。比如发布容器的80端口到主机的8080端口。

    ENV

    1. ENV <key> <value>
    2. ENV <key>=<value> …

    ENV指令设置环境变量的值为。这个值可以用在之后的Dockerfile命令中并替换命令中所有的环境变量。
    ENV指令有两种形式,第一种是 ,将设置单个变量为一个值。第一个空格之后的整个字符将设置为这个变量的值 – 包括空格和引号等字符。
    第二种形式允许同时设置多个变量。设置这种形式使用了等于号,第一种是没有使用的。像命令行解析,引号和反斜杠可以用来包括空格。
    例如:

    1. ENV myName="John Doe" myDog=Rex The Dog
    2.     myCat=fluffy

    和:

    1. ENV myName John Doe
    2. ENV myDog Rex The Dog
    3. ENV myCat fluffy

    虽然这两种形式得到的结果一样,但推荐第一种,因为它只生成了一个缓存层。
    使用ENV设置环境变量将永久存在于从该镜像运行的容器中。你可以使用docker inspect查看这些值,也可以使用

    1. docker run –env <key>=<value>

    更改它们。

    注意:环境变量的存在可能引起意外的副作用。例如,设置ENV DEBIAN_FRONTEND noninteractive可能会使在基于Debian镜像的apt-get用户感到困惑。要在单个命令中使用变量,可以使用

    1. RUN <key>=<value> <command>

    Dockerfile参考(9) – LABEL添加元数据到镜像

    格式:

    1. LABEL <key>=<value> <key>=<value> <key>=<value> …

    LABEL指令添加元数据到一个镜像。一个LABEL是一个键值对。要在LABEL值中包含空格,使用双引号和反斜杠,就像在命令行解析中一样。一些可用的示例:

    1. LABEL "com.example.vendor"="ACME Incorporated"
    2. LABEL com.example.label-with-value="foo"
    3. LABEL version="1.0"
    4. LABEL description="This text illustrates
    5. that label-values can span multiple lines."

    一个镜像可以有多个label。要指定多个labels,Docker推荐尽可能地把多个labels合并到一个LABEL指令中去。每一个LABEL指令会生成一个新的镜像层,如果你使用多个label,将导致构建出一个低效的镜像。这个示例只生成单个镜像层。

    1. LABEL multi.label1="value1" multi.label2="value2" other="value3"

    上面的示例可以重写为:

    1. LABEL multi.label1="value1"
    2.       multi.label2="value2"
    3.       other="value3"

    label是累积的,包括FROM镜像的lable。如果Docker遇到一个label/key已经存在,那么新的值将覆盖这个label/key。
    要查看一个镜像的label,使用docker inspect命令。

    1. "Labels": {
    2.     "com.example.vendor": "ACME Incorporated"
    3.     "com.example.label-with-value": "foo",
    4.     "version": "1.0",
    5.     "description": "This text illustrates that label-values can span multiple lines.",
    6.     "multi.label1": "value1",
    7.     "multi.label2": "value2",
    8.     "other": "value3"
    9. },