Dockerfile Instructions(指令)

FROM

在 Docker Hub (https://hub.docker.com/explore/) 上有非常多的高质量的官方镜 像, 有可以直接拿来使用的服务类的镜像,如
nginx 、 redis 、 mongo 、 mysql 、 httpd 、 php 、 tomcat 等; 也有 一些方便开发、构建、运行各种语言应用的镜像,如
node 、 openjdk 、 python 、 ruby 、 golang 等。 可以在其中寻找一个最 符合我们最终目标的镜像为基础镜像进行定制。 如果没有找到对应服务的镜像,官 方镜像中还提供了一些更为基础的操作系统镜像,如
ubuntu 、 debian 、 centos 、 fedora 、 alpine 等,这些操作系统的软 件库为我们提供了更广阔的扩展空间。

  1. FROM指令是一个最重要的指令,且必须为Dockerfile文件开篇的第一个非注释行,用于为映像文件后见过程指定基准镜像,后续的指令运行与此基准镜像所提供的运行环境。
  2. 实践中,基准镜像可以是任何可用镜像文件,默认情况下,docker build 会在docker 主机上查找指定的镜像文件,在其不存在时,则会从Docker Hub Registry上拉取所需的镜像文件。

    当然也并不全到Docker Hub Registry上拉取镜像,如果指明了repository服务器地址也会到对应的Registry上拉取镜像。

Syntax(语法)

  • FROM[:]或
  • FROM@
    • :指定作为base imgae的名称;
    • :base image的标签,为可选项,省略时默认为latest

      尽量避免用名称指定镜像名称,如果有心之人将你的镜像删掉,换上他的镜像那是很危险的。 所以我们用哈希码来指定镜像FROM@

除了选择现有镜像为基础镜像外,Docker 还存在一个特殊的镜像,名为scratch。这个镜像是虚拟的概念,并不实际存在,它表示一个空白的镜像。

  1. FROM scratch
  2. ...

如果你以 scratch 为基础镜像的话,意味着你不以任何镜像为基础,接下来所写 的指令将作为镜像第一层开始存在。

MAINTANIER(已废弃 depreacted)

  • 用于让Dockerfile制作者提供本人的详细信息。
  • Dockerfile并不限制MAINTAINER指令可在出现的位置,但推荐将其放置域FROM指令之后。

Syntax(语法)

  • MAINTAINER<authtor’s detail>
    • <author’s detail>可是任何文本信息,但约定俗成地使用这名称及邮件地址。
    • MAINTAINER”zhangkaizhangkai@qq.com

示例

  1. FROM [--platform=<platform>] <image> [AS <name>]
  2. FROM [--platform=<platform>] <image>[:<tag>] [AS <name>]
  3. FROM [--platform=<platform>] <image>[@<digest>] [AS <name>]

LABEL

  • LABEL指令将元数据添加到镜像中
  • LABEL指令向镜像添加元数据,LABEL是一个键值对。
  • 要在LABEL中包含空格,请使用引号和反斜杠,就像命令行解析一样。
  • 一个镜像可以有多个标签
  • 可以在一行上指定多个标签

Syntax(语法)

  • LABEL = = <key>= .<br />
    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."
    在镜像构建后并成功运行容器,可以通过inspect查看
    1. # docker image inspect --format='' myimage
    2. {
    3. "com.example.vendor": "ACME Incorporated",
    4. "com.example.label-with-value": "foo",
    5. "version": "1.0",
    6. "description": "This text illustrates that label-values can span multiple lines."
    7. }

COPY

image.png格式:

  1. COPY [--chown=<user>:<group>] <源路径1>... <目标路径>
  2. COPY [--chown=<user>:<group>] ["<源路径1>",... "<目标路径>"]

[—chown=:]:可选参数,用户改变复制到容器内文件的拥有者和属组。
<源路径>:源文件或者源目录,这里可以是通配符表达式,其通配符规则要满足 Go 的 filepath.Match 规则。例如:

  1. COPY hom* /mydir/
  2. COPY hom?.txt /mydir/

<目标路径>:容器内的指定路径,该路径不用事先建好,路径不存在的话,会自动创建。
目标路径不需要事先创建,如果目录不存在会在复制文件前先行创建缺失目录。 此外,还需要注意一点,使用 COPY 指令,源文件的各种元数据都会保留。比如读、写、执行权限、文件变更时间等。这个特性对于镜像定制很有用。特别是构建相关文件都在使用 Git 进行管理的时候。

ADD

image.png
ADD 指令和 COPY 的使用格式一致(同样需求下,官方推荐使用 COPY)。功能也类似,不同之处如下:

  • ADD 的优点:在执行 <源文件> 为 tar 压缩文件的话,压缩格式为 gzip, bzip2 以及 xz 的情况下,会自动复制并解压到 <目标路径>。
  • ADD 的缺点:在不解压的前提下,无法复制 tar 压缩文件。会令镜像构建缓存失效,从而可能会令镜像构建变得比较缓慢。具体是否使用,可以根据是否需要自动解压来决定。 ``` ADD http://nginx.org/download/nginx-1.20.1.tar.gz . ADD http://nginx.org/download/nginx-1.20.1.tar.gz ./NGINX/ ADD nginx-1.20.1.tar.gz ./NGINX/
比如 <源路径> 可以是一个 URL ,这种情况下,Docker 引擎会试图去下载这个 链接的文件放到 <目标路径> 去。下载后的文件权限自动设置为 600 .
<a name="Rev77"></a>
## WORKDIR
![image.png](https://cdn.nlark.com/yuque/0/2021/png/21527842/1622785906208-ab8cc8b2-6b76-41c9-ac1a-2d4851f7a33f.png#align=left&display=inline&height=285&margin=%5Bobject%20Object%5D&name=image.png&originHeight=285&originWidth=876&size=291305&status=done&style=none&width=876)

栗子: ADD http://nginx.org/download/nginx-1.20.1.tar.gz ./NGINX/ #例子一

WORKDIR /NGINX/ #例子二 ADD http://nginx.org/download/nginx-1.20.1.tar.gz ./ 此处目标地址为当前路径因为WORKDOR定义了下面ADD指令的当前工作路径。

```
WORKDIR /NGINX/                                                 #例子二
ADD http://nginx.org/download/nginx-1.20.1.tar.gz ./            此处目标地址为当前路径因为WORKDOR定义了下面ADD指令的当前工作路径。
WORLDIR /NGINX/html/                                                  此处注明下一条指令执行的工作目录为/NGINX/html/
ADD /html/ ./

解释当Dockerfile中出现多个WORKDIR时,每个WORKDIR后面的指令都会以指令前面最近的WORKDIR定义的工作目录为当前目录。

VOLUME

image.png

Dockerfile中只能指定Docker中的卷路径,不能指定宿主机的路径,属于Docker存储里的Docker-managed volume

# this is test image

FROM busybox:1.1.1

LABEL maintainer="zhangkai <zhangkai.com>"

COPY yum.repos.d /etc/yum.repos.d/
# ADD http://nginx.org/download/nginx-1.20.1.tar.gz .
ADD nginx-1.20.1.tar.gz ./NGINX/

VOLUME /BUSY-VOLUME-URL/

image.png

EXPOSE

image.png
EXPOSE指令通知 Docker 容器在运行时侦听指定的网络端口。可以指定端口是监听TCP还是UDP,如果不指定协议,默认为TCP。
EXPOSE指令实际上并未发布端口。它充当构建镜像的人和运行容器的人之间的一种文档,关于打算发布哪些端口。要在运行容器时实际发布端口,请使用-pon 标志docker run 来发布和映射一个或多个端口,或者使用-P标志来发布所有暴露的端口并将它们映射到高阶端口。
默认情况下,EXPOSE假定 TCP。您还可以指定 UDP:

EXPOSE 80/udp

要同时在 TCP 和 UDP 上公开,请包含两行:

EXPOSE 80/tcp
EXPOSE 80/udp

在这种情况下,如果您使用-Pwith docker run,则该端口将为 TCP 公开一次,为 UDP 公开一次。请记住,-P在主机上使用临时高阶主机端口,因此 TCP 和 UDP 的端口将不同。

  • P :是容器内部端口随机映射到主机的高端口。
  • -p : 是容器内部端口绑定到指定的主机端口。

无论EXPOSE设置如何,您都可以在运行时使用-p标志覆盖它们。例如

$ docker run -p 80:80/tcp -p 80:80/udp ...

此外,在早期 Docker 版本中还有一个特殊的用处。以前所有容器都运行于默认桥 接网络中,因此所有容器互相之间都可以直接访问,这样存在一定的安全性问题。 于是有了一个 Docker 引擎参数 --icc=false ,当指定该参数后,容器间将默认 无法互访,除非互相间使用了 --links 参数的容器才可以互通,并且只有镜像中 EXPOSE 所声明的端口才可以被访问。这个 --icc=false的用法,在引入了 docker network 后已经基本不用了,通过自定义网络可以很轻松的实现容器间 的互联与隔离。

ENV

image.png

# this is test image

FROM busybox:1.1.1

LABEL maintainer="zhangkai <zhangkai.com>"

COPY yum.repos.d /etc/yum.repos.d/

# ADD http://nginx.org/download/nginx-1.20.1.tar.gz .
ADD nginx-1.20.1.tar.gz ./NGINX/

VOLUME /BUSY-VOLUME-URL/

加上变量如下

# this is test image

FROM busybox:1.1.1

LABEL maintainer="zhangkai <zhangkai.com>"

ENV DDD=/etc/yum.repos.d/ \              “\”为换行 
        WWW="nginx-1.20.1"

COPY yum.repos.d ${DDD:-/etc/yum.repos.d/}           #此处使用DDD变量,并且说明DDD有值时为所赋予的值,无值时默认/etc/yum.repos.d/

# ADD http://nginx.org/download/nginx-1.20.1.tar.gz .
ADD ${WWW}.tar.gz ./NGINX/       #此处使用WWW变量

VOLUME /BUSY-VOLUME-URL/

在docker run命令运行容器时更改ENV

docker run --env <key>=<value>

image.png
image.png

抛出一个栗子:在命令行中启动起来的nginx是作为shell的子进程存在,用户启动并创建进程的接口是shell,这些是我们与用户交互的接口,所以当我们打开命令行提示符就相当于在运行一个shell进程(在命令行下所创建的进程都应该是shell的子进程),并且有些进程还要占据当前shell的终端设备(命令提示符界面),我们如果想要将其置于后台运行应该在命令后面加上$ 例如#command $ 。但是此举并不能将其剥离shell,它仍旧是shell的子进程,倘若exit退出shell那么shell的子进程讲全部停掉,如果基于shell启动,那么该应用程序就不是ID号为1 的进程了,而容器中启动一个进程的的先启动一个shell如果shell退出停掉,那么基于shell启动的进程也会被停掉。


有这样的一个办法:利用shell启动也可以,shell的ID也可以为1,但是命令中要加入exec 这样exec顶替shel的ID为1,取代shell,shell退出,它就是ID为1的进程。 这样我们可以基于镜像启动一个容器时,也可以不基于shell启动进程且保持主进程ID为1 的条件,我们可以使用exec实现

下列指令可以支持环境变量展开: ADD 、 COPY 、 ENV 、 EXPOSE 、 LABEL 、 USER 、 WORKDIR 、 VOLUME 、 STOPSIGNAL 、 ONBUILD 。

CMD

image.png
之前介绍容器的时候曾经说过,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 去启动后台服务,容器内没有后台服务的概念。甚至在容器内去使用 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;"]

举个栗子 1
Dockerfile写法如下:
image.png
dockers inspect
image.png
由此可以看出,此容器默认启动**/bin/sh**然后加上-c选项启动为/bin/httpd -f -h ${WEB DOC ROOT}
接下来docker run 看下(我们看到命令提示符卡到了那里,这是因为httpd没有交互式接口)
image.png
尽管没有进入交互式,但是通过进程可以看到httpd进程存在并运行的命令为/bin/htttpd
image.png
此时我们可以使用-exec命令启动交互式
image.png
并且PID为1,所以使用docker stop时改进程可以接收到信号,并且停掉。

举个栗子 2
使用CMD第二种语法json数组的格式
image.png
docker inspect看下,此时我们发现没有了/bin/sh
image.png
运行发现报错了:
image.png
所以我们应该知道在第二种语法中,默认不会运行成shell的子进程,因此Dockerfile中描述的${WEB DOC ROOT}是无法执行的,core并不认识这是什么?所以我们修改为
image.png**

ENTRYPOINT

image.png
举个栗子
image.png
其中的ls更改了镜像中默认要运行的程序。 我们可以在docker run 中加入运行命令覆盖Dockerfile定义的命令。有时有些命令无法覆盖的话我们就用ENTRYPOINT
image.png
ls并没有执行成功。因为没有加-exec所以没用进入交互模式
image.png
在Dockerfile中同时定义了CMD和ENTRYPOINT,CMD的内容将当作参数传递给ENTRYPOINT,而此处语法正如CMD语法中的第三项。具体执行情况是先运行ENTRYPOINT当ENTRYPOINT中命令运行完才去运行CM中的参数。
注意:json数组中要使用双引号。
image.png image.png
我们发现问题:Entrypoint中出现了两处/bin/sh 因为什么呢?
image.png
因为ENTRYPOINT这种写法默认使用/bin/sh来执行命令的。而在Dockerfile中我们又为其定义了一处。如果想要只使用自己定义的/bin/sh我们可以这样写(ENTRYPOINT的第二种语法,不默认为使用/bin/sh )
image.png
image.png

RUN

image.png
Dockerfile中每一个指令都会建立一层,RUN也不例外。每一个RUN的行为,就和刚才我们手工建立镜像的过程一样:新建立一层,在其上执行这些命令,执行结束后,commit这一层的修改,构成新的镜像。

FROM debian:jessie

RUN apt-get update
RUN apt-get install -y gcc libc6-dev make
RUN wget -O redis.tar.gz "http://download.redis.io/releases/redis-3.2.5.tar.gz"
RUN mkdir -p /usr/src/redis
RUN tar -xzf redis.tar.gz -C /usr/src/redis --strip-components=1
RUN make -C /usr/src/redis
RUN make -C /usr/src/redis install

而上面的这种写法,创建了 7 层镜像。这是完全没有意义的,而且很多运行时不需 要的东西,都被装进了镜像里,比如编译环境、更新的软件包等等。结果就是产生 非常臃肿、非常多层的镜像,不仅仅增加了构建部署的时间,也很容易出错。
image.png
Union FS 是有最大层数限制的,比如 AUFS,曾经是最大不得超过 42 层,现在是 不得超过 127 层。
此外,还可以看到这一组命令的最后添加了清理工作的命令,删除了为了编译构建 所需要的软件,清理了所有下载、展开的文件,并且还清理了 apt 缓存文件。这是很重要的一步,我们之前说过,镜像是多层存储,每一层的东西并不会在下一层被删除,会一直跟随着镜像。因此镜像构建时,一定要确保每一层只添加真正需要添加的东西,任何无关的东西都应该清理掉。

这里没有使用很多个 RUN 对一一 对应不同的命令,而是仅仅使用一个 RUN 指令,并使用 && 将各个所需命令串联起来,并且,这里为了格式化还进行了换行。Dockerfile 支持 Shell 类的行尾添加 \的 命令换行方式,以及行首#进行注释的格式。良好的格式,比如换行、缩进、注 释等,会让维护、排障更为容易

撰写一个shell脚本
image.png

#!/bin/sh
#
cat > /etc/nginx/conf.d/www.conf <<EOF
server {
        server_name ${HOSTNAME};
        listen ${IP:-0.0.0.0}:${PORT:-80};
        root ${NGX_DOC_ROOT:-usr/share/nginx/html};
}
EOF
赋予执行权限   chmod +x shell文件名

编写Dockerfile如下

# nginx-test-images
FROM nginx:1.14-alpine
LABEL maintainer="zhangkai <zhangkai@qq.com>"

ENV NGX_DOC_ROOT="/data/web/html/"

ADD binsh.sh /bin/

CMD ["/usr/sbin/nginx/","-g","daemon off;"]         #daemon off是nginx中的参数表示前台运行,daemon on表示后台运行  
                                                    #/usr/sbin/nginx表示(使用which nginx查到的路径)执行nginx中的给定命令的绝对路径
                                                    #此处表示docker build 后根据镜像启动容器时将nginx启动为容器内主进程。
ENTRYPOINT ["/bin/binsh.sh"]                        # 此处表示先执行ENTRYPOINT中的命令,也就是先执行binsh.sh脚本,然后执行CMD中的启动nginx前台运行为主进程。

docker build -t myweb:1 ./
docker run -it --name myweb --rm myweb:1 /bin/sh
image.png
也可以在docker run 时传递参数:

docker run --name myweb --rm -it -p 80:80 myweb:1

image.png
然后复制会话,在另一个进程中运行

docker exec -it myweb /bin/sh

#解释:相当于在第一个命令提示符会话窗口中将镜像启动为容器,因为nginx中没有交互模式,所以复制命令提示符会话窗口执行docker exec -it myweb /bin/sh,其中exec建立交互式,然后以nginx为主程序启动一个/bin/sh进行交互。

image.png
因为使用host模式,所以使用宿主ip:80即可访问到nginx默认网页
image.png

USER

image.png

USER <user>[:<group>]

或者

USER <UID>[:<GID>]

所述USER指令集运行的图像和用于任何时要使用的用户名(或UID)和任选的所述用户组(或GID) RUNCMDENTRYPOINT它后面的指令Dockerfile

请注意,为用户指定组时,用户将具有指定的组成员资格。任何其他配置的组成员资格都将被忽略。 警告 当用户没有主要组时,映像(或下一个说明)将与该root组一起运行。 在 Windows 上,如果用户不是内置帐户,则必须先创建该用户。这可以通过net user作为 Dockerfile 的一部分调用的命令来完成。

FROM microsoft/windowsservercore
# Create Windows user in the container
RUN net user /add patrick
# Set it for subsequent commands
USER patrick

如果以 root 执行的脚本,在执行期间希望改变身份,比如希望以某个已经建立 好的用户来运行某个服务进程,不要使用 su 或者 sudo ,这些都需要比较麻烦 的配置,而且在 TTY 缺失的环境下经常出错。建议使用 gosu ,可以从一下项目网站看到进一步的信息:https://github.com/tianon/gosu

# 建立 redis 用户,并使用 gosu 换另一个用户执行命令
RUN groupadd -r redis && useradd -r -g redis redis
# 下载 gosu
RUN wget -O /usr/local/bin/gosu "https://github.com/tianon/gosu/releases/download/1.7/gosu-amd64" \
&& chmod +x /usr/local/bin/gosu \
&& gosu nobody true
# 设置 CMD,并以另外的用户执行
CMD [ "exec", "gosu", "redis", "redis-server" ]

HEALTHCHECK

HEALTHCHECK指令有两种形式:

  • HEALTHCHECK [OPTIONS] CMD command (通过在容器内运行命令来检查容器健康状况)
  • HEALTHCHECK NONE (禁用从基础镜像继承的任何健康检查)

HEALTHCHECK指令告诉 Docker 如何测试容器以检查它是否仍在工作。这可以检测诸如 Web 服务器陷入无限循环并且无法处理新连接的情况,即使服务器进程仍在运行。
当容器指定了健康检查时,除了其正常状态之外,它还具有健康状态。此状态最初为starting。每当健康检查通过时,它就会变成healthy(无论它之前处于什么状态)。连续失败一定次数后,变为unhealthy
之前可以出现的选项CMD有:

  • --interval=DURATION(默认值:30s)每隔多长时间
  • --timeout=DURATION(默认值:30s)超时等待时长
  • --start-period=DURATION(默认值:0s)有的程序在容器起来后过一段时间才能初始化完成启动起来,所以需要设定一个等待时间
  • --retries=N(默认值:3)检查多少次

运行状况检查将在容器启动后首先运行interval秒,然后在每次之前的检查完成后再次运行interval秒。
如果检查的单次运行时间超过timeout秒,则认为检查失败。
它需要重试连续的健康检查失败才能考虑容器unhealthy
start period为需要时间引导的容器提供初始化时间。在此期间的探测失败将不计入最大重试次数。但是,如果在启动期间健康检查成功,则认为容器已启动,所有连续失败都将计入最大重试次数。
HEALTHCHECK一个 Dockerfile 中只能有一条指令。如果您列出多个,则只有最后一个HEALTHCHECK才会生效。
CMD关键字后的命令可以是 shell 命令(例如HEALTHCHECK CMD /bin/check-running)或exec数组(与其他 Dockerfile 命令一样;ENTRYPOINT有关详细信息,请参见 eg )。
命令的退出状态指示容器的健康状态。可能的值为:

  • 0:成功 - 容器运行良好,可以使用
  • 1:不健康 - 容器不能正常工作
  • 2:reserved - 不要使用这个退出代码

例如,每五分钟左右检查一次网络服务器是否能够在三秒钟内为站点的主页提供服务:

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

为了帮助调试失败的探测,命令在 stdout 或 stderr 上写入的任何输出文本(UTF-8 编码)都将存储在健康状态中,并且可以使用 docker inspect来查看. 此类输出应保持简短(当前仅存储前 4096 个字节)。
当容器的健康状态发生变化时,health_status会生成具有新状态的事件。

shell

SHELL ["executable", "parameters"]

SHELL指令允许覆盖用于命令的shell形式的默认 shell 。Linux 上的默认 shell 是["/bin/sh", "-c"],Windows 上是["cmd", "/S", "/C"]. 该SHELL指令必须以 JSON 格式写入 Dockerfile。
SHELL指令在 Windows 上特别有用,因为 Windows 有两种常用且截然不同的本机 shell:cmdpowershell,以及可用的备用 shell,包括sh.
SHELL指令可以出现多次。每条SHELL指令都会覆盖所有先前的SHELL指令,并影响所有后续指令。例如:

FROM microsoft/windowsservercore
# Executed as cmd /S /C echo default
RUN echo default
# Executed as cmd /S /C powershell -command Write-Host default
RUN powershell -command Write-Host default
# Executed as powershell -command Write-Host hello
SHELL ["powershell", "-command"]
RUN Write-Host hello
# Executed as cmd /S /C echo hello
SHELL ["cmd", "/S", "/C"]
RUN echo hello

SHELL当在 Dockerfile 中使用它们的shell形式时,以下指令可能会受到该指令的 影 响:RUN,CMDENTRYPOINT.
以下示例是在 Windows 上找到的常见模式,可以使用SHELL指令进行精简:

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

docker 调用的命令将是:

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

这是低效的,原因有二。首先,调用了一个不必要的 cmd.exe 命令处理器(又名 shell)。其次,shell 形式RUN中的每条指令都需要一个额外的命令前缀。powershell -command
为了使这更有效,可以采用两种机制之一。一种是使用 RUN 命令的 JSON 形式,例如:

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

虽然 JSON 形式是明确的,并且不使用不必要的 cmd.exe,但它确实需要通过双引号和转义来更加冗长。另一种机制是使用SHELL指令和外壳形式,为 Windows 用户提供更自然的语法,尤其是与escape解析器指令结合使用时:

# escape=`
FROM microsoft/nanoserver
SHELL ["powershell","-command"]
RUN New-Item -ItemType Directory C:\Example
ADD Execute-MyCmdlet.ps1 c:\example\
RUN c:\example\Execute-MyCmdlet -sample 'hello world'

导致:

PS E:\myproject> docker build -t shell .
Sending build context to Docker daemon 4.096 kB
Step 1/5 : FROM microsoft/nanoserver
 ---> 22738ff49c6d
Step 2/5 : SHELL powershell -command
 ---> Running in 6fcdb6855ae2
 ---> 6331462d4300
Removing intermediate container 6fcdb6855ae2
Step 3/5 : RUN New-Item -ItemType Directory C:\Example
 ---> Running in d0eef8386e97
    Directory: C:\
Mode         LastWriteTime              Length Name
----         -------------              ------ ----
d-----       10/28/2016  11:26 AM              Example
 ---> 3f2fbf1395d9
Removing intermediate container d0eef8386e97
Step 4/5 : ADD Execute-MyCmdlet.ps1 c:\example\
 ---> a955b2621c31
Removing intermediate container b825593d39fc
Step 5/5 : RUN c:\example\Execute-MyCmdlet 'hello world'
 ---> Running in be6d8e63fe75
hello world
 ---> 8e559e9bf424
Removing intermediate container be6d8e63fe75
Successfully built 8e559e9bf424
PS E:\myproject>

SHELL指令还可用于修改 shell 的运行方式。例如,SHELL cmd /S /C /V:ON|OFF在 Windows 上使用,可以修改延迟的环境变量扩展语义。
如果SHELL需要备用 shell,例如、和其他zsh,该指令也可以在 Linux 上使用。csh``tcsh

STOPSIGNAL

STOPSIGNAL指令设置系统调用信号,该信号将被发送到容器退出。

STOPSIGNAL signal

STOPSIGNAL指令设置将发送到容器退出的系统调用信号。该信号可以是与内核系统调用表中的位置匹配的有效无符号数,例如 9,或格式为 SIGNAME 的信号名称,例如 SIGKILL。

ARG

格式: ARG <参数名>[=<默认值>]构建参数和 ENV 的效果一样,都是设置环境变量。所不同的是, ARG 所设置的 构建环境的环境变量,在将来容器运行时是不会存在这些环境变量的。但是不要因此就使用 ARG 保存密码之类的信息,因为 docker history 还是可以看到所有 值的。 Dockerfile 中的 ARG 指令是定义参数名称,以及定义其默认值。该默认值可 以在构建命令 docker build 中用 --build-arg <参数名>=<值>来覆盖。 在 1.13 之前的版本,要求 --build-arg 中的参数名,必须在 Dockerfile 中 用 ARG 定义过了,换句话说,就是--build-arg 指定的参数,必须在 Dockerfile 中使用了。如果对应参数没有被使用,则会报错退出构建。从 1.13 开始,这种严格的限制被放开,不再报错退出,而是显示警告信息,并继续构建。 这对于使用 CI 系统,用同样的构建流程构建不同的 Dockerfile 的时候比较有 帮助,避免构建命令必须根据每个 Dockerfile 的内容修改
image.png
docker build
image.png
dcoker inspect
image.png

ONBUILD

image.png
格式:ONBUILD <其它指令>
ONBUILD 是一个特殊的指令,它后面跟的是其它指令,比如 RUN , COPY 等,而这些指令,在当前镜像构建时并不会被执行。只有当以当前镜像为基础镜像,去构建下一级镜像的时候才会被执行。
Dockerfile 中的其它指令都是为了定制当前镜像而准备的,唯有 ONBUILD 是为了帮助别人定制自己而准备的。
假设我们要制作 Node.js 所写的应用的镜像。我们都知道 Node.js 使用 npm 进行包管理,所有依赖、配置、启动信息等会放到 package.json 文件里。在拿到程序代码后,需要先进行 npm install 才可以获得所有需要的依赖。然后就可以过 npm start 来启动应用。因此,一般来说会这样写 Dockerfile :

FROM node:slim
RUN "mkdir /app"
WORKDIR /app
COPY ./package.json /app
RUN [ "npm", "install" ]
COPY . /app/
CMD [ "npm", "start" ]

把这个 Dockerfile 放到 Node.js 项目的根目录,构建好镜像后,就可以直接拿来启动容器运行。但是如果我们还有第二个 Node.js 项目也差不多呢?好吧,那就再把这个 Dockerfile 复制到第二个项目里。那如果有第三个项目呢?再复制么?文件的副本越多,版本控制就越困难,让我们继续看这样的场景维护的问题。
如果第一个 Node.js 项目在开发过程中,发现这个 Dockerfile 里存在问题,比如敲错字了、或者需要安装额外的包,然后开发人员修复了这个 Dockerfile ,再次构建,问题解决。第一个项目没问题了,但是第二个项目呢?虽然最初
Dockerfile 是复制、粘贴自第一个项目的,但是并不会因为第一个项目修复了他们的 Dockerfile ,而第二个项目的 Dockerfile 就会被自动修复。
那么我们可不可以做一个基础镜像,然后各个项目使用这个基础镜像呢?这样基础镜像更新,各个项目不用同步 Dockerfile 的变化,重新构建后就继承了基础镜像的更新?好吧,可以,让我们看看这样的结果。那么上面的这个 Dockerfile就会变为:

FROM node:slim
RUN "mkdir /app"
WORKDIR /app
CMD [ "npm", "start" ]

这里我们把项目相关的构建指令拿出来,放到子项目里去。假设这个基础镜像的名字为 my-node 的话,各个项目内的自己的 Dockerfile 就变为:

FROM my-node
COPY ./package.json /app
RUN [ "npm", "install" ]
COPY . /app/

基础镜像变化后,各个项目都用这个 Dockerfile 重新构建镜像,会继承基础镜像的更新。
那么,问题解决了么?没有。准确说,只解决了一半。如果这个 Dockerfile 里面有些东西需要调整呢?比如 npm install 都需要加一些参数,那怎么办?这一行 RUN 是不可能放入基础镜像的,因为涉及到了当前项目的
./package.json ,难道又要一个个修改么?所以说,这样制作基础镜像,只解
决了原来的 Dockerfile 的前4条指令的变化问题,而后面三条指令的变化则完
全没办法处理。
ONBUILD 可以解决这个问题。让我们用 ONBUILD 重新写一下基础镜像的
Dockerfile :

89FROM node:slim
RUN "mkdir /app"
WORKDIR /app
ONBUILD COPY ./package.json /app
ONBUILD RUN [ "npm", "install" ]
ONBUILD COPY . /app/
CMD [ "npm", "start" ]

这次我们回到原始的 Dockerfile ,但是这次将项目相关的指令加上ONBUILD ,这样在构建基础镜像的时候,这三行并不会被执行。然后各个项目的Dockerfile 就变成了简单地:
FROM my-node
是的,只有这么一行。当在各个项目目录中,用这个只有一行的 Dockerfile 构建镜像时,之前基础镜像的那三行 ONBUILD 就会开始执行,成功的将当前项目的代码复制进镜像、并且针对本项目执行 npm install ,生成应用镜像。