当前位置:   article > 正文

Docker 镜像制作之DockerFile

dockerfile

一、DockerFile介绍

Dockerfile 是一个用来构建镜像的文本文件,文本内容包含了一条条构建镜像所需的指令和说明。dockerfile仅仅是用来制作镜像的源码文件,是构建容器过程中的指令,docker能够读取dockerfile的指定进行自动构建容器,基于dockerfile制作镜像,每一个指令都会创建一个镜像层,即镜像都是多层叠加而成,因此,层越多,效率越低,创建镜像,层越少越好。因此能在一个指令完成的动作尽量通过一个指令定义。

二、DockerFile 指令

2.1、FROM –指定基础镜像,必须为第一个命令

FROM指令是最重要的一个且必须为 Dockerfile文件开篇的第一个非注释行,用于为映像文件构建过程指定基准镜像,后续的指令运行于此基准镜像所提供的运行环境 .

实践中,基准镜像可以是任何可用镜像文件,默认情况下, docker build会在 docker主机上查找指定的镜像文件,在其不存在时,则会从 Docker Hub Registry上拉取所需的镜像文件 .如果找不到指定的镜像文件, docker build会返回一个错误信息

FROM 语法

FROM <repository>[:<tag>] 或者
FROM <repository>@<digest>

<repository>:指定作为base image的名称
<tag>:base image的标签,为可选项,省略时默认为 latest;
<digest>为校验码

示例:
  FROM mysql:5.6
注:
  tag或digest是可选的,如果不使用这两个值时,会使用latest版本的基础镜像
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13

2.2、MAINTANIER –维护者信息(已经废弃) – ->LABEL

用于让镜像制作者提供本人的详细信息

Dockerfile并不限制 MAINTAINER指令可在出现的位置,但推荐将其放置于 FROM指令之后 .

语法:

MAINTAINER  <authtor's detail> l <author's detail>可是任何文本信息,但约定俗成地使用作者名称及邮件地址,如
MAINTAINER "sunny <sunny@ghbsunny.cn>"
一般把MAINTAINER放在FROM后面

示例:
    MAINTAINER Jasper Xu
    MAINTAINER sorex@163.com
    MAINTAINER Jasper Xu <sorex@163.com>
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8

2.3、LABEL–用于为镜像添加元数据

LABEL用于为镜像添加元数据,元数以键值对的形式指定:

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

使用LABEL指定元数据时,一条LABEL指定可以指定一或多条元数据,指定多条元数据时不同元数据之间通过空格分隔。推荐将所有的元数据通过一条LABEL指令指定,以免生成过多的中间镜像。
如,通过LABEL指定一些元数据:

LABEL version=“1.0” description=“这是一个Web服务器” by=“IT笔录”
指定后可以通过docker inspect查看:

docker inspect itbilu/test
"Labels": {
    "version": "1.0",
    "description": "这是一个Web服务器",
    "by": "IT笔录"
},
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6

2.4、COPY–用于从 Docker主机复制文件至创建的新映像文件

语法

COPY <src> ... <dest>. COPY ["<src>",... "<dest>"]

<src>:要复制的源文件或目录,支持使用通配符
<dest>:目标路径,即正在创建的 image的文件系统路径;建议为 <dest>使用绝对路径,<dest>绝对路径为镜像中的路径,而不是宿主机的路径。否则, COPY指定则以 WORKDIR为其起始路径

注意:在路径中有空白字符时,通常使用第二种格式 .

文件复制准则
<src>必须是build上下文中的路径,即只能放在workshop这个工作目录下,不能是其父目录中的文件
如果<src>是目录,其内部文件或者子目录会被递归复制,但<src>目录自身不会被复制
如果指定了多个<src>,或在<src>中使用了通配符,则<dest>必须是一个目录,且dest目录必须以/结尾
如果<dest>事先不存在,它将会被自动创建,这包括其父目录路径
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12

例子

创建一个目录img1,在该目录下新建index.html文件用于镜像制作的素材文件,在img1下新建Dockerfile文件,把index.html拷贝到新镜像里

copy是指在当前的img1工作目录中,准备好要添加到新镜像的文件放到这个img1下面,copy过程实际是基于dockerfile在后台启动一个容器,把工作目录当做卷挂载到后台启动的容器,然后再把这些准备好的文件(img1目录下)拷贝到后台容器,然后基于这个容器制作新镜像,所以,镜像的制作过程是基于指定的镜像来制作

COPY文件

[root@node1 ~]# mkdir img1/
[root@node1 ~]# cd img1/
[root@node1 img1]# ls
[root@node1 img1]# vim index.html
<h1>zhujingxing </h1>
[root@node1 img1]# vim Dockerfile
# Description: test image
FROM busybox:latest
MAINTAINER "zisefeizhu <zisefeizhu@zhujingxing.com>"
#LABEL maintainer="zisefeizhu <zisefeizhu@zhujingxing.com>"
COPY index.html /data/web/html/
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11

Dockerfile制作完成后,用命令build制作基于dockerfile的新镜像

[root@node1 img1]# docker build  -t tinyhttpd:v0.1-1 ./
Sending build context to Docker daemon  3.072kB
Step 1/3 : FROM busybox:latest                                       //FROM
 ---> 59788edf1f3e
Step 2/3 : MAINTAINER "zisefeizhu <zisefeizhu@zhujingxing.com>"      //MAINTAINER
 ---> Using cache
 ---> e0a3a7c1de10
Step 3/3 : COPY index.html /data/web/html/                           //COPY
 ---> 35c00f8b6408 
Successfully built 35c00f8b6408
Successfully tagged tinyhttpd:v0.1-1
[root@node1 img1]# docker image ls            //查看新生成的镜像
REPOSITORY                                          TAG                 IMAGE ID            CREATED             SIZE
tinyhttpd                                           v0.1-1              35c00f8b6408        9 seconds ago       1.15MB
//启动新生成的镜像,在容器内部有目录/data/web/html/,并且有文件index.html
[root@node1 ~]# docker run --name tinyweb1 --rm tinyhttpd:v0.1-1 cat /data/web/html/index.html
<h1>zhujingxing </h1>
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17

COPY目录

[root@node1 img1]# cp -r /etc/yum.repos.d/ ./
[root@node1 img1]# ls 
Dockerfile  index.html  yum.repos.d
[root@node1 img1]# ls yum.repos.d/
CentOS-Base.repo  CentOS-Debuginfo.repo  CentOS-Media.repo    CentOS-Vault.repo  docker-ce.repo.cp
CentOS-CR.repo    CentOS-fasttrack.repo  CentOS-Sources.repo  docker-ce.repo     epel.repo
[root@node1 img1]# vim Dockerfile 
# Description: test image
FROM busybox:latest
MAINTAINER "zisefeizhu <zisefeizhu@zhujingxing.com>"
#LABEL maintainer="zisefeizhu <zisefeizhu@zhujingxing.com>"
COPY index.html /data/web/html/
COPY yum.repos.d /etc/yum.repos.d/
[root@node1 img1]# docker build  -t tinyhttpd:v0.1-2 ./
Sending build context to Docker daemon  28.67kB
Step 1/4 : FROM busybox:latest
 ---> 59788edf1f3e
Step 2/4 : MAINTAINER "zisefeizhu <zisefeizhu@zhujingxing.com>"
 ---> Using cache
 ---> e0a3a7c1de10
Step 3/4 : COPY index.html /data/web/html/
 ---> Using cache
 ---> 35c00f8b6408
Step 4/4 : COPY yum.repos.d /etc/yum.repos.d/
 ---> 6250436df8e7
Successfully built 6250436df8e7
Successfully tagged tinyhttpd:v0.1-2
[root@node1 ~]# docker run --name tinyweb1 --rm tinyhttpd:v0.1-2 ls /etc/yum.repos.d/
CentOS-Base.repo
CentOS-CR.repo
CentOS-Debuginfo.repo
CentOS-Media.repo
CentOS-Sources.repo
CentOS-Vault.repo
CentOS-fasttrack.repo
docker-ce.repo
docker-ce.repo.cp
epel.repo
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38

2.5、ADD:将本地文件添加到容器中,tar类型文件会自动解压(网络压缩资源不会被解压),可以访问网络资源,类似wget

ADD指令类似于 COPY指令, ADD支持使用 TAR文件和 URL路径

语法

ADD <src> ... <dest>或

ADD ["<src>",... "<dest>"]

示例:
    ADD hom* /mydir/          # 添加所有以"hom"开头的文件
    ADD hom?.txt /mydir/      # ? 替代一个单字符,例如:"home.txt"
    ADD test relativeDir/     # 添加 "test" 到 `WORKDIR`/relativeDir/
    ADD test /absoluteDir/    # 添加 "test" 到 /absoluteDir/
操作准则

同COPY指令的4点准则

如果<src>为URL且<dest>不以/结尾,则<src>指定的文件将被下载并直接被创建为<dest>;如果<dest>以/结尾,则文件名URL指定的文件将被直接下载,并保存为<dest>/<filename>,注意,URL不能是ftp格式的url
如果<src>是一个本地系统上的压缩格式的tar文件,它将被展开为一个目录,其行为类似于“tar -x”命令,然后,通过URL获取到的tar文件将不会自动展开
如果<src>有多个,或其间接或直接使用了通配符,则<dest>必须是一个以/结尾的目录路径;如果<dest>不以/结尾,则其被视作一个普通文件,<src>的内容将被直接写入到<dest>
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16

例子

[root@node1 img1]# vim Dockerfile 
ADD http://nginx.org/download/nginx-1.15.5.tar.gz /usr/local/src/

[root@node1 img1]# docker build  -t tinyhttpd:v0.1-3 ./
Step 5/5 : ADD http://nginx.org/download/nginx-1.15.5.tar.gz /usr/local/src/
Downloading  1.025MB/1.025MB
 ---> 77406c81872f
Successfully built 77406c81872f
Successfully tagged tinyhttpd:v0.1-3

[root@node1 ~]# docker run --name tinyweb1 --rm tinyhttpd:v0.1-3 ls /usr/local/src
nginx-1.15.5.tar.gz

[root@node1 img1]# wget http://nginx.org/download/nginx-1.15.5.tar.gz

[root@node1 img1]# vim Dockerfile 
ADD nginx-1.15.5.tar.gz /usr/local/src/
[root@node1 img1]# docker build  -t tinyhttpd:v0.1-4 ./
Step 5/5 : ADD nginx-1.15.5.tar.gz /usr/local/src/
 ---> ff39a60ceccb
Successfully built ff39a60ceccb
Successfully tagged tinyhttpd:v0.1-4
[root@node1 ~]# docker run --name tinyweb1 --rm tinyhttpd:v0.1-4 ls /usr/local/src
nginx-1.15.5
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
2.5.1、COPY和ADD的区别
COPY命令格式为COPY [--chown=:] <源路径1>... <目标路径> ,功能是复制指令,从上下文目录中复制文件或者目录到容器里指定路径。而ADD指令的命令格式跟COPY相同。其中上下文目录指的是docker build 命令的 PATH 或 URL 指定的路径中的文件的集合。

ADD在执行 <源文件> 为 tar 压缩文件的话,压缩格式为 gzip, bzip2 以及 xz 的情况下,会自动复制并解压到 <目标路径>,但在不解压的前提下,无法复制 tar 压缩文件。会令镜像构建缓存失效,从而可能会令镜像构建变得比较缓慢。具体是否使用,可以根据是否需要自动解压来决定。

当版本1.0的Docker发布时,包含了新的COPY指令。与ADD不同的是,COPY直接将文件和文件夹从构建上下文复制到容器中。COPY不支持URL作为参数,因此它不能用于从远程位置下载文件。任何想要复制到容器中的东西都必须存在于本地构建上下文中。

另外,COPY对压缩文件没有特别的处理。如果您复制归档文件,它将完全按照出现在构建上下文中的方式落入容器中,而不会尝试解压缩它。

COPY 命令是为最基本的用法设计的,概念清晰,操作简单。而 ADD 命令基本上是 COPY 命令的超集,可以实现一些方便、酷炫的拷贝操作。ADD 命令在增加了功能的同时也增加了使用它的复杂度,比如从 url 拷贝压缩文件时弊大于利。在不用自动解压工作或者添加远程文件到镜像中,同样需求下,官方推荐使用 COPY进行指令操作。
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9

2.6、WORKDIR–工作目录,类似于cd命令

workdir为工作目录,指当前容器环境的工作目录,用于为 Dockerfile中所有的 RUN、CMD、ENTRYPOINT、COPY和 ADD指定设定工作目录

语法

WORKDIR  <dirpath>

在Dockerfile文件中, WORKDIR指令可出现多次,其路径也可以为相对路径,不过,其是相对此前一个 WORKDIR指令指定的路径;另外, WORKDIR也可调用由 ENV指定定义的变量 .例如

WORKDIR /var/log
WORKDIR  $STATEPATH

示例:
    WORKDIR /a  (这时工作目录为/a)
    WORKDIR b  (这时工作目录为/a/b)
    WORKDIR c  (这时工作目录为/a/b/c)
注:
  通过WORKDIR设置工作目录后,Dockerfile中其后的命令RUN、CMD、ENTRYPOINT、ADD、COPY等命令都会在该目录下执行。在使用docker run 运行容器时,可以通过-w参数覆盖构建时所设置的工作目录。
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13

例子

指定workdir为/usr/local,相当于是容器启动后,会把工作目录切换到/usr/local这个workdir路径下,而不是默认的根目录,如下例子,则相对路径 ./src/ 的绝对路径为容器的/usr/local/src,制作镜像时,把nginx包拷贝到/usr/local/src,把tomcat包解压到/usr/local/src下面

[root@node1 img1]# vim Dockerfile
FROM busybox:1.27.2
MAINTAINER "zisefeizhu <zisefeizhu@zhujingxing.com>"
WORKDIR "/usr/local"
ADD http://nginx.org/download/nginx-1.14.0.tar.gz  ./src/
ADD apache-tomcat-8.0.47.tar.gz ./

启动容器并检查
[root@node1 img1]# docker run -it --rm --name testworkdir testworkdir:v1
/usr/local # ls
apache-tomcat-8.0.47  src
/usr/local # ls src
nginx-1.14.0.tar.gz
/usr/local #
容器启动后,工作路径直接切换为/usr/local
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15

2.7、VOLUME–用于指定持久化目录

定义卷,只能是docker管理的卷,,VOLUME为容器上的目录,用于在 image中创建一个挂载点目录,以挂载 Docker host上的卷或其它容器上的卷

语法

VOLUME <mountpoint>或
VOLUME ["<mountpoint>"]

示例:
    VOLUME ["/data"]
    VOLUME ["/var/www", "/var/log/apache2", "/etc/apache2"
如果挂载点目录路径下此前在文件存在, docker run命令会在卷挂载完成后将此前的所有文件复制到新挂载的卷中

注:
一个卷可以存在于一个或多个容器的指定目录,该目录可以绕过联合文件系统,并具有以下功能:
1 卷可以容器间共享和重用
2 容器并不一定要和其它容器共享卷
3 修改卷后会立即生效
4 对卷的修改不会对镜像产生影响
5 卷会一直存在,直到没有任何容器在使用它
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15

例子

[root@node1 ~]# vim img1/Dockerfile 
#ADD http://nginx.org/download/nginx-1.15.5.tar.gz /usr/local/src/
WORKDIR /usr/locl/
ADD nginx-1.15.5.tar.gz ./src
VOLUME /data/mysql/

[root@node1 img1]# docker build  -t tinyhttpd:v0.1-5 ./
Step 5/7 : WORKDIR /usr/locl/
 ---> Running in 08a4d481c0f9
Removing intermediate container 08a4d481c0f9
 ---> b9246b4e4f2b
Step 6/7 : ADD nginx-1.15.5.tar.gz ./src
 ---> b5e78864d438
Step 7/7 : VOLUME /data/mysql/
 ---> Running in afbc37ddff3e
Removing intermediate container afbc37ddff3e
 ---> e6cbde99a92d
 查看法一:
[root@node1 ~]# docker run --name tinyweb1 --rm tinyhttpd:v0.1-5 mount 
/dev/sda3 on /data/mysql type xfs (rw,seclabel,relatime,attr2,inode64,noquota)
查看法二:
[root@node1 ~]# docker run --name tinyweb1 --rm tinyhttpd:v0.1-5 sleep 60
[root@node1 img1]# docker inspect tinyweb1
"Mounts": [
            {
                "Destination": "/data/mysql",
            }
        ],
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28

2.8、EXPOSE–指定于外界交互的端口

暴露指定端口,用于为容器打开指定要监听的端口以实现与外部通信

语法

EXPOSE <port>[/<protocol>] [<port>[/<protocol>] ...] l

其中<protocol>用于指定传输层协议,可为 tcp或udp二者之一,默认为 TCP协议
EXPOSE指令可一次指定多个端口,但是不能指定暴露为宿主机的指定端口,因为指定的宿主机端口可能已经被占用,因此这里使用随机端口,例如

EXPOSE 11211/udp 11211/tcp

注:
  EXPOSE并不会让容器的端口访问到主机。要使其可访问,需要在docker run运行容器时通过-p来发布这些端口,或通过-P参数来发布EXPOSE导出的所有端口
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9

例子:

[root@node1 img1]# vim Dockerfile 
EXPOSE 80/tcp
[root@node1 ~]# docker run --name tinyweb1 --rm tinyhttpd:v0.1-6 /bin/httpd -f -h /data/web/html
[root@node1 ~]# docker inspect tinyweb1                                                                                               "IPAddress": "172.17.0.2",  
[root@node1 ~]# curl 172.17.0.2
<h1>zhujingxing </h1>
[root@node1 ~]# docker port tinyweb1   //没有端口信息   即没有正真暴露出来

###启动并暴露端口,注意,启动容器要跟大写P选项-P来暴露
[root@node1 ~]# docker run --name tinyweb1 --rm -P  tinyhttpd:v0.1-6 /bin/httpd -f -h /data/web/html
[root@node1 ~]# curl 172.17.0.2
<h1>zhujingxing </h1>
[root@node1 ~]# docker port tinyweb1
80/tcp -> 0.0.0.0:32768
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14

2.9、ENV–设置环境变量

ENV用于为镜像定义所需的环境变量,并可被 Dockerfile文件中位于其后的其它指令(如 ENV、ADD、COPY等)所调用 ,即先定义后调用

调用格式为 v a r i a b l e n a m e 或 variable_name或 variablename{variable_name}

语法

ENV <key> <value>
或  
ENV <key>=<value> ... .

第一种格式中, <key>之后的所有内容均会被视作其 <value>的组成部分,因此一次只能设置一个变量
第二种格式,可用一次设置多个变量,每个变量为一个“<key>=<value>”的键值对,如果<value>包含空格,可以以反斜线(\)进行转义,也可通过对<value>加引号进行标识;另外反斜线也可以用于续行;
定义多个变量时,建议使用第二种方式,以便在同一层中完成所有功能

示例:
    ENV myName John Doe
    ENV myDog Rex The Dog
    ENV myCat=fluffy
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12

例子

[root@node1 img1]# cat Dockerfile 

#ENV  DOC_ROOt=/data/web/html/    单个文件 
ENV  DOC_ROOt=/data/web/html/ \     多个文件
     WEB_SERVER_PACKAGE="nginx-1.15.5"

COPY index.html ${DOC_ROOT:-/data/web/html/}
COPY yum.repos.d /etc/yum.repos.d/
#ADD http://nginx.org/download/nginx-1.15.5.tar.gz /usr/local/src/
WORKDIR /usr/locl/
ADD ${WEB_SERVER_PACKAGE}.tar.gz ./src/

###创建镜像
[root@node1 img1]# docker build  -t tinyhttpd:v0.1-7 ./
Sending build context to Docker daemon  1.054MB
Step 1/9 : FROM busybox:latest
 ---> 59788edf1f3e
Step 2/9 : MAINTAINER "zisefeizhu <zisefeizhu@zhujingxing.com>"
 ---> Using cache
 ---> e0a3a7c1de10
Step 3/9 : ENV  DOC_ROOt=/data/web/html/      WEB_SERVER_PACKAGE="nginx-1.15.5"
 ---> Using cache
 ---> 61cc545b9111
Step 4/9 : COPY index.html ${DOC_ROOT:-/data/web/html/}
 ---> Using cache
 ---> a962e0fa3d54
Step 5/9 : COPY yum.repos.d /etc/yum.repos.d/
 ---> Using cache
 ---> c6e3e674f856
Step 6/9 : WORKDIR /usr/locl/
 ---> Using cache
 ---> b2574695b705
Step 7/9 : ADD ${WEB_SERVER_PACKAGE}.tar.gz ./src/
 ---> Using cache
 ---> fbf4219523dc
Step 8/9 : VOLUME /data/mysql/
 ---> Using cache
 ---> cbe2f8c57722
Step 9/9 : EXPOSE 80/tcp
 ---> Using cache
 ---> 3edf30b6f3d3
Successfully built 3edf30b6f3d3
Successfully tagged tinyhttpd:v0.1-7

###运行容器验证
[root@node1 ~]# docker run -it --name tinyweb1 --rm -P  tinyhttpd:v0.1-7
/usr/locl # ls 
src
/usr/locl # cd src/
/usr/locl/src # ls
nginx-1.15.5
/usr/locl/src # exit
[root@node1 ~]# docker run --name tinyweb1 --rm -P  tinyhttpd:v0.1-7 ls /data/web/html
index.html

有些变量在运行为容器时依然有用,因此需要把那些变量在运行为容器时重新定义为一个新的值,如果变量很多,可以放到一个文件中进行定义,使用参数 --env-list(docker run --help )实现,通过文件来加载环境变量

[root@node1 ~]# docker run --name tinyweb1 --rm -P  tinyhttpd:v0.1-7 printenv
PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin
HOSTNAME=39efa2eeac77
DOC_ROOt=/data/web/html/
WEB_SERVER_PACKAGE=nginx-1.15.5
HOME=/root

[root@node1 ~]# docker run --name tinyweb1 --rm -P -e WEB_SERVER_PACKAGE="nginx-1.15-6" tinyhttpd:v0.1-7 printenv
WEB_SERVER_PACKAGE=nginx-1.15-6
DOC_ROOt=/data/web/html/
HOME=/root
[root@node1 ~]# docker run --name tinyweb1 --rm -P -e WEB_SERVER_PACKAGE="nginx-1.15-6" tinyhttpd:v0.1-7 printenv && ls /usr/local/src
WEB_SERVER_PACKAGE=nginx-1.15-6
DOC_ROOt=/data/web/html/
HOME=/root
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63
  • 64
  • 65
  • 66
  • 67
  • 68
  • 69
  • 70
  • 71
  • 72

设置时区

ENV TZ=Asia/Shanghai
  • 1

2.10、RUN–构建镜像时执行的命令

RUN用于指定 docker build过程中运行的程序,其可以是任何命令,但是这里有个限定,一般为基础镜像可以运行的命令,如基础镜像为centos,安装软件命令为yum而不是ubuntu里的apt-get命令

RUN和CMD都可以改变容器运行的命令程序,但是运行的时间节点有区别,RUN表示在docker build运行的命令,而CMD是将镜像启动为容器运行的命令。因为一个容器正常只用来运行一个程序,因此CMD一般只有一条命令,如果CMD配置多个,则只有最后一条命令生效。而RUN可以有多个。

语法

shell执行:
RUN <command>
或 
exec执行:
RUN ["<executable>", "<param1>", "<param2>"]

第一种格式中,<command>通常是一个shell命令,且以“/bin/sh -c”作为父进程来运行它,这意味着此进程在容器中的PID不为1,不能接收Unix信号,因此,当使用 docker stop <container>命令停止容器时,此进程接收不到SIGTERM信号;

第二种语法格式中的参数是一个JSON格式的数组,其中<executable>为要运行的命令,后面的<paramN>为传递给命令的选项或参数;然而,此种格式指定的命令不会以“/bin/sh -c”来发起,表示这种命令在容器中直接运行,不会作为shell的子进程,因此常见的shell操作如变量替换以及通配符(?,*等)替换将不会进行,不过,如果要运行的没能力依赖此shell特性的话,可以将其替换为类似下面的格式

注意:json数组中使用双引号

RUN ["/bin/bash","-C","<executable>","<paraml>"]

示例:
    RUN ["executable", "param1", "param2"]
    RUN apk update
    RUN ["/etc/execfile", "arg1", "arg1"]
注:
  RUN指令创建的中间镜像会被缓存,并会在下次构建中使用。如果不想使用这些缓存镜像,可以在构建时指定--no-cache参数,如:docker build --no-cache
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20

例子

###把下载的nginx打包文件,用RUN命令来展开
###编辑dockerfile

[root@node1 img1]# vim Dockerfile 
# Description: test image
FROM busybox:latest
MAINTAINER "zisefeizhu <zisefeizhu@zhujingxing.com>"
#LABEL maintainer="zisefeizhu <zisefeizhu@zhujingxing.com>"
ENV  DOC_ROOt=/data/web/html/ \
     WEB_SERVER_PACKAGE="nginx-1.15.5.tar.gz"

COPY index.html ${DOC_ROOT:-/data/web/html/}
COPY yum.repos.d /etc/yum.repos.d/
ADD http://nginx.org/download/${WEB_SERVER_PACKAGE} /usr/local/src/
WORKDIR /usr/local/
#ADD ${WEB_SERVER_PACKAGE}.tar.gz ./src/
VOLUME /data/mysql/
EXPOSE 80/tcp
RUN cd /usr/local/src && \
    tar xf ${WEB_SERVER_PACKAGE}

###创建镜像
[root@node1 img1]# docker build  -t tinyhttpd:v0.1-9 ./

###运行容器并验证
[root@node1 ~]# docker run --name tinyweb1 --rm -P -e WEB_SERVER_PACKAGE="nginx-1.15-6" -it tinyhttpd:v0.1-9 ls /usr/local/src 
nginx-1.15.5   nginx-1.15.5.tar.gz
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27

如果RUN的命令很多,就用&&符号连接多个命令,少构建镜像层,提高容器的效率

例子如下

###基础镜像为centos,RUN多个命令
###由于安装是到互联网上的仓库进行安装,所以,建议把centos的yum源配置为本地,即创建镜像时,把yum的配置有本地仓库源配置在CentOS-Base.repo文件放在imp1下面,配置文件配置ADD拷贝一份到新建镜像的/etc/yum.repos.d目录下,因为经常默认会优先加载CentOS-Base.repo下的包,但是不建议使用这个方法,除非本地仓库有足够的包解决依赖关系,否则建议仅使用默认的即可
###编辑dockerfile

[root@node1 img1]# vim Dockerfile
FROM centos:7.3.1611
MAINTAINER "zisefeizhu <zisefeizhu@zhujingxing.com>"
ENV nginx_ver=1.14.0
ENV nginx_url=http://nginx.org/download/nginx-${nginx_ver}.tar.gz
WORKDIR "/usr/local/src"
ADD CentOS-Base.repo  /etc/yum.repos.d/
ADD ${nginx_url} /usr/local/src/
RUN tar xf nginx-${nginx_ver}.tar.gz && \
        yum -y install gcc pcre-devel openssl-devel make &&  \   
        cd nginx-${nginx_ver} && \   
        ./configure && make && make install

###创建镜像
[root@node1 img1]# docker build -t nginx:v1 ./

###运行容器,启动nginx进程
[root@node1 img1]# docker run -it --rm --name nginxv1 nginx:v1
[root@ccedfdf5e63f src]# /usr/local/nginx/sbin/nginx

###此时,nginx进程运行于后台,不建议这么做,因为容器的进程要运行于前台模式,否则容器会终止,nginx运行于前台,需要在nginx的配置文件nginx.conf里添加配置项
vi /usr/local/nginx/conf/nginx.conf
daemon off;

###这样使得nginx运行于前台,再次运行nginx,则运行于前台,或者通过-g选项,在运行nginx的全局配置模式之后再运行某些参数,注意off后面的冒号
[root@ccedfdf5e63f local]# /usr/local/nginx/sbin/nginx -g "daemon off;"
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30

2.11、CMD–构建容器后调用,也就是在容器启动时才进行调用。

类似于 RUN指令, CMD指令也可用于运行任何命令或应用程序,不过,二者的运行时间点不同. RUN指令运行于映像文件构建过程中,而 CMD指令运行于基于 Dockerfile构建出的新映像文件启动一个容器时 . CMD指令的首要目的在于为启动的容器指定默认要运行的程序,且其运行结束后,容器也将终止;不过, CMD指定的命令其可以被 docker run的命令行选项所覆盖 .在Dockerfile中可以存在多个 CMD指令,但仅最后一个会生效

语法

CMD command param1 param2 (执行shell内部命令)
CMD ["executable","param1","param2"] (执行可执行文件,优先)
CMD ["param1","param2"] (设置了ENTRYPOINT,则直接调用ENTRYPOINT添加参数)

前两种语法格式的意义同 RUN
第三种则用于为 ENTRYPOINT指令提供默认参数

示例:
    CMD echo "This is a test." | wc -
    CMD ["/usr/bin/wc","--help"]
注:
   CMD不同于RUN,CMD用于指定在容器启动时所要执行的命令,而RUN用于指定镜像构建时所要执行的命令。
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12

例子

一:

[root@node1 img1]# mkdir ~/img2
[root@node1 ~]# cd img2
[root@node1 img2]# vim Dockerfile
FROM busybox
LABEL maintainer="zhujingxing <zisefeizhu@zhujingxing.com>" app="httpd"
ENV WEB_DOC_ROOT="/data/web/html/"
RUN mkdir -p  $WEB_DOC_ROOT && \
    echo '<h1> zhujingxing</h1>' > ${WEB_DOC_ROOT}/index.html
CMD /bin/httpd -f -h ${WEB_DOC_ROOT}
[root@node1 img2]# docker build  -t tinyhttpd:v0.2-1 ./
[root@node1 img2]# docker image inspect tinyhttpd:v0.2-1

          "Cmd": [
                "/bin/sh",
                "-c",
                "/bin/httpd -f -h ${WEB_DOC_ROOT}"
            ],

[root@node1 ~]# docker run --name tinyweb2 -it --rm -P tinyhttpd:v0.2-1    //卡到这里不动了

root@node1 ~]# docker ps
CONTAINER ID        IMAGE               COMMAND                  CREATED             STATUS              PORTS               NAMES
ea15f4a3623d        tinyhttpd:v0.2-1    "/bin/sh -c '/bin/ht…"   26 seconds ago      Up 25 seconds                           tinyweb2
[root@node1 ~]# docker exec -it tinyweb2 /bin/sh     //exec 还可以登进去
/ # ps 
PID   USER     TIME  COMMAND
    1 root      0:00 /bin/httpd -f -h /data/web/html/
    6 root      0:00 /bin/sh
   11 root      0:00 ps
/ # printenv
WEB_DOC_ROOT=/data/web/html/
HOSTNAME=ea15f4a3623d
SHLVL=1
HOME=/root
TERM=xterm
PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin
PWD=/
/ # exit
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38

二:

[root@node1 img2]# vim Dockerfile 
FROM busybox
LABEL maintainer="zhujingxing <zisefeizhu@zhujingxing.com>" app="httpd"
ENV WEB_DOC_ROOT="/data/web/html/"
RUN mkdir -p  $WEB_DOC_ROOT && \
    echo '<h1> zhujingxing</h1>' > ${WEB_DOC_ROOT}/index.html
#CMD /bin/httpd -f -h ${WEB_DOC_ROOT}
CMD [ "/bin/httpd","-f","-h ${WEB_DOC_ROOT}"]      //改成json数组格式 

[root@node1 img2]# docker build  -t tinyhttpd:v0.2-2 ./

[root@node1 img2]# docker image inspect tinyhttpd:v0.2-2

"Cmd": [
                "/bin/httpd",
                "-f",
                "-h ${WEB_DOC_ROOT}"
            ],
[root@node1 ~]# docker run --name tinyweb2 -it --rm -P tinyhttpd:v0.2-2   //报错  json默认不会启动shell进程
httpd: can't change directory to ' ${WEB_DOC_ROOT}': No such file or directory
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20

[root@node1 img2]#vim Dockerfile 
FROM busybox
LABEL maintainer="zhujingxing <zisefeizhu@zhujingxing.com>" app="httpd"
ENV WEB_DOC_ROOT="/data/web/html/"
RUN mkdir -p  $WEB_DOC_ROOT && \
    echo '<h1> zhujingxing</h1>' > ${WEB_DOC_ROOT}/index.html
#CMD /bin/httpd -f -h ${WEB_DOC_ROOT}
#CMD [ "/bin/httpd","-f","-h ${WEB_DOC_ROOT}"]
CMD [ "/bin/sh","-c","/bin/httpd","-f","-h ${WEB_DOC_ROOT}"]

[root@node1 img2]# docker build  -t tinyhttpd:v0.2-3 ./

[root@node1 ~]# docker run --name tinyweb2 -it  -P tinyhttpd:v0.2-3  //没报错但直接退出终端
[root@node1 ~]# docker ps -a
CONTAINER ID        IMAGE                     COMMAND                  CREATED             STATUS                         PORTS               NAMES
bda42611547e        tinyhttpd:v0.2-3          "/bin/sh -c /bin/htt…"   2 seconds ago       Exited (0) 1 second ago                            tinyweb2

[root@node1 ~]# docker logs tinyweb2   //没输出,说明没有结果,可能是找不到路径
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18

[root@node1 img2]# vim Dockerfile 
FROM busybox
LABEL maintainer="zhujingxing <zisefeizhu@zhujingxing.com>" app="httpd"
ENV WEB_DOC_ROOT="/data/web/html/"
RUN mkdir -p  $WEB_DOC_ROOT && \
    echo '<h1> zhujingxing</h1>' > ${WEB_DOC_ROOT}/index.html
#CMD /bin/httpd -f -h ${WEB_DOC_ROOT}
#CMD [ "/bin/httpd","-f","-h ${WEB_DOC_ROOT}"]
#CMD [ "/bin/sh","-c","/bin/httpd","-f","-h ${WEB_DOC_ROOT}"]
CMD [ "/bin/sh","-c","/bin/httpd","-f","-h /data/web/html"]
[root@node1 img2]# docker build  -t tinyhttpd:v0.2-4 ./
[root@node1 ~]# docker rm tinyweb2
tinyweb2 
[root@node1 ~]# docker run --name tinyweb2 -it --rm -P tinyhttpd:v0.2-4    //这应该是没问题的,可能是id号导致的
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14

###编译安装nginx,并将镜像的默认命令修改为nginx启动于前台,暴露80口
###编辑dockerfile

vim Dockerfile
FROM centos:7.3.1611
MAINTAINER "sunny <sunny@ghbsunny.cn>"
ENV nginx_ver=1.14.0
ENV nginx_url=http://nginx.org/download/nginx-${nginx_ver}.tar.gz
WORKDIR "/usr/local/src"
EXPOSE 80/tcp
ADD ${nginx_url} /usr/local/src/RUN tar xf nginx-${nginx_ver}.tar.gz && yum -y install gcc pcre-devel openssl-devel make \
    && cd nginx-${nginx_ver} && ./configure && make && make install
CMD ["/usr/local/nginx/sbin/nginx","-g","daemon off;"]

###制作镜像
[root@node1 img2]# docker build -t nginx:v3 ./

###启动容器并测试
[root@node1 img2]# docker run -it --rm --name nginxv3 nginx:v3

###测试,容器的ip 为 172.17.0.2,得到nginx的测试页
[root@node1 yum.repos.d]# curl 172.17.0.2

###查看容器80口被暴露为哪个口
[root@node1 yum.repos.d]# docker port nginxv3
80/tcp -> 0.0.0.0:32772
[root@node1 yum.repos.d]# curl 10.0.0.210:32772

###注意,CMD在dockerfile里写的命令,如果启动容器的命令行里执行命令,则会把dockerfile里的命令覆盖掉,如下,容器启动后,执行/bin/bash,而不是启动nginx于前台
[root@node1 img2]# docker run -it --rm -P --name nginxv3 nginx:v3 /bin/bash
[root@5f2f4b930df3 src]# ss -ntlp

###如果dockerfile指定的CMD不允许覆盖,则使用ENTRYPOINT
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33

2.12、ENTRYPOINT–配置容器,使其可执行化。配合CMD可省去"application",只使用参数。

类似 CMD指令的功能,用于为容器指定默认运行程序,从而使得容器像是一个单独的可执行程序

与CMD不同的是,由 ENTRYPOINT启动的程序不会被 docker run命令行指定的参数所覆盖,而且,这些命令行参数会被当作参数传递给 ENTRYPOINT指定指定的程序 .不过, docker run命令的 --entrypoint选项的参数可覆盖ENTRYPOINT指令指定的程序

语法

ENTRYPOINT ["executable", "param1", "param2"] (可执行文件, 优先)
ENTRYPOINT command param1 param2 (shell内部命令)

示例:
    FROM ubuntu
    ENTRYPOINT ["top", "-b"]
    CMD ["-c"]
docker run 命令传入的命令参数会覆盖CMD指令的内容并且附加到ENTRYPOINT命令最后做为其参数使用 . Dockerfile文件中也可以存在多个 ENTRYPOINT指令,但仅有最后一个会生效

注:
ENTRYPOINT与CMD非常类似,不同的是通过docker run执行的命令不会覆盖ENTRYPOINT,而docker run命令中指定的任何参数,都会被当做参数再次传递给ENTRYPOINT。Dockerfile中只允许有一个ENTRYPOINT命令,多指定时会覆盖前面的设置,而只执行最后的ENTRYPOINT指令。
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11

例子

[root@node1 img3]# vim Dockerfile 

FROM nginx:1.14-alpine
LABEL maintainer="zhujingxing  <zisefeizhu@zhujingxing>"
ENV NGX_DOC_ROOT='/data/web/html/'
ADD index.html ${NGX_DOC_ROOT}
ADD entrypoint.sh /bin/
CMD ["/usr/sbin/nginx","-g","daemon off;"]     //注:双引号
ENTRYPOINT ["/bin/entrypoint.sh"]

[root@node1 img3]# vim entrypoint.sh 

#!/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
exec "$@"

[root@node1 img3]# docker build -t myweb:v0.3-6 ./
[root@node1 img3]# docker run --name myweb1 --rm -P -e "PORT=8080" myweb:v0.3-6
[root@node1 ~]# docker exec -it myweb1 /bin/sh
/ # netstat -lnt
Active Internet connections (only servers)
Proto Recv-Q Send-Q Local Address           Foreign Address         State       
tcp        0      0 0.0.0.0:8080            0.0.0.0:*               LISTEN      
tcp        0      0 0.0.0.0:80              0.0.0.0:*               LISTEN      
/ # ps
PID   USER     TIME   COMMAND
    1 root       0:00 nginx: master process /usr/sbin/nginx -g daemon off;
    8 nginx      0:00 nginx: worker process
    9 root       0:00 /bin/sh
   15 root       0:00 ps
/ # wget -O - -q adf1e144ec50
<h1>zhujingxing</h1>
/ # exit
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39

2.13、HEALTHCHECK --docker容器运行健康检查

语法形式:

HEALTHCHECK [OPTIONS] CMD command (通过在容器中运行一个命令执行健康检查)
HEALTHCHECK NONE (禁用从基本镜像继承的任何健康检查)

通过HEALTHCHECK,我们可以知道如何测试一个容器查检一个它是否在工作,比如检测一个web 服务是否陷入死循环,不能处理新的连接、即使服务器进程仍在运行
当一个窗口指定了健康检查时、除了正常状态之外、还会有一个健康状态作为初始、如果检查通过、则会变成健康状态、如果经过了一定次数的连续故障、则会变成非健康状态
在CMD之前可以出现的选项如下:

--interval=DURATION (default: 30s)
--timeout=DURATION (default: 30s)
--start-period=DURATION (default: 0s)
--retries=N (default: 3)

注:
启动周期为需要时间启动的容器提供初始化时间。 在此期间的探测失败不会计入最大重试次数。 但是,如果在启动期间运行状况检查成功,则认为容器已启动,并且所有连续的故障都将计入最大重试次数
单次运行检查花费时间超过timeout指定时间、判定失败
每个Dockerfile中只能存在一个HEALTHCHECK指令,如果有多个则最后一个起作用
HEALTHCHECK CMD后面的命令既可以是一个shell命令、也可以是一个exec 的数组
命令的退出状态显示出容器的健康状态、如下:
0: success - the container is healthy and ready for use
1: unhealthy - the container is not working correctly
2: reserved(保留的) - do not use this exit code 

实例:每隔五分钟检查一次网络服务器是否能够在三秒钟内为该网站的主页面提供服务
HEALTHCHECK --interval=5m --timeout=3s CMD curl -f http://localhost/ || exit 1
为方便故障探测调试、检测命令通过stdout或者stderr输出的文本都会被缓存在健康状态中(缓存大小为4096字节)、并可以通过docker inspect查询
当容器的运行状况发生变化时,新的状态会生成一个health_status事件
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26

成功的例子:

[root@node1 img3]# cat Dockerfile 
FROM nginx:1.14-alpine
LABEL maintainer="zhujingxing  <zisefeizhu@zhujingxing>"
ENV NGX_DOC_ROOT='/data/web/html/'
ADD index.html ${NGX_DOC_ROOT}
ADD entrypoint.sh /bin/
EXPOSE 80/TCP
HEALTHCHECK --start-period=3s CMD wget -O - -q http://${IP:-0.0.0.0}:${PORT:80}/
CMD ["/usr/sbin/nginx","-g","daemon off;"]
ENTRYPOINT ["/bin/entrypoint.sh"]
[root@node1 img3]# docker build -t myweb:v0.3-7 ./
[root@node1 img3]# docker run --name myweb1 --rm -P -e "PORT=8080" myweb:v0.3-7 

[root@node1 ~]# docker exec -it myweb1 /bin/sh

/ # netstat -tnl
Active Internet connections (only servers)
Proto Recv-Q Send-Q Local Address           Foreign Address         State       
tcp        0      0 0.0.0.0:8080            0.0.0.0:*               LISTEN      
tcp        0      0 0.0.0.0:80              0.0.0.0:*               LISTEN      
/ # wget -O

[root@node1 img3]# docker run --name myweb1 --rm -P -e "PORT=8080" myweb:v0.3-7 
127.0.0.1 - - [28/Oct/2018:13:15:35 +0000] "GET / HTTP/1.1" 200 612 "-" "Wget" "-"
127.0.0.1 - - [28/Oct/2018:13:15:43 +0000] "GET / HTTP/1.1" 200 612 "-" "Wget" "-"
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25

失败的例子

[root@node1 img3]# vim Dockerfile 

FROM nginx:1.14-alpine
LABEL maintainer="zhujingxing  <zisefeizhu@zhujingxing>"
ENV NGX_DOC_ROOT='/data/web/html/'
ADD index.html ${NGX_DOC_ROOT}
ADD entrypoint.sh /bin/
EXPOSE 80/TCP
HEALTHCHECK --start-period=3s CMD wget -O - -q http://${IP:-0.0.0.0}:10080/
CMD ["/usr/sbin/nginx","-g","daemon off;"]
ENTRYPOINT ["/bin/entrypoint.sh"]
[root@node1 img3]# docker build -t myweb:v0.3-8 ./

[root@node1 img3]# docker run --name myweb1 --rm -P -e "PORT=8080" myweb:v0.3-8 //三个周期默认1.5分钟后报错
[root@node1 ~]# docker exec -it myweb1 /bin/sh
/ # netstat -tnl
Active Internet connections (only servers)
Proto Recv-Q Send-Q Local Address           Foreign Address         State       
tcp        0      0 0.0.0.0:8080            0.0.0.0:*               LISTEN      
tcp        0      0 0.0.0.0:80              0.0.0.0:*               LISTEN     
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20

2.14、ARG–用于指定传递给构建运行时的变量

语法:

ARG <name>[=<default value>]

如,通过ARG指定两个变量:
ARG site
ARG build_user=IT笔录

以上我们指定了 site 和 build_user 两个变量,其中 build_user 指定了默认值。在使用 docker build 构建镜像时,可以通过 --build-arg <varname>=<value> 参数来指定或重设置这些变量的值。

docker build --build-arg site=itiblu.com -t itbilu/test .
这样我们构建了 itbilu/test 镜像,其中site会被设置为 itbilu.com,由于没有指定 build_user,其值将是默认值 IT 笔录。
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10

2.15、STOPSIGNAL–用于设置停止容器所要发送的系统调用信号

语法:

STOPSIGNAL signal
  • 1

所使用的信号必须是内核系统调用表中的合法的值,如:SIGKILL。

2.16、SHELL–用于设置执行命令(shell式)所使用的的默认 shell 类型

语法:

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

SHELL在Windows环境下比较有用,Windows 下通常会有 cmd 和 powershell 两种 shell,可能还会有 sh。这时就可以通过 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
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11

2.17、USER–指定运行容器时的用户名或 UID

后续的 RUN 也会使用指定用户。使用USER指定用户时,可以使用用户名、UID或GID,或是两者的组合。当服务不需要管理员权限时,可以通过该命令指定运行用户。并且可以在之前创建所需要的用户**

USER用于指定运行 image时的或运行 Dockerfile中任何 RUN、CMD或 ENTRYPOINT指令指定的程序时的用户名或 UID ,即改变容器中运行程序的身份

默认情况下, container的运行身份为 root用户

语法

USER  <UID>|<UserName>

需要注意的是, <UID>可以为任意数字,但实践中其必须为 /etc/passwd中某用户的有效 UID,否则, docker run命令将运行失败
使用USER指定用户时,可以使用用户名、UID 或 GID,或是两者的组合。以下都是合法的指定试:

USER user
USER user:group
USER uid
USER uid:gid
USER user:gid
USER uid:group
使用USER指定用户后,Dockerfile 中其后的命令 RUN、CMD、ENTRYPOINT 都将使用该用户。镜像构建完成后,通过 docker run 运行容器时,可以通过 -u 参数来覆盖所指定的用户
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12

2.18、ONBUILD–用于设置镜像触发器

ONBUILD 用于在 Dockerfile中定义一个触发器 . 用来指定运行docker指令

Dockerfile用于 build映像文件,此映像文件亦可作为 base image被另一个 Dockerfile用作 FROM指令的参数,并以之构建新的映像文件

.在后面的这个 Dockerfile中的 FROM指令在 build过程中被执行时,将会 “触发 ”创建其 base image的Dockerfile文件中的 ONBUILD指令定义的触发器

语法

ONBUILD <INSTRUCTION>

尽管任何指令都可注册成为触发器指令,但是ONBUILD不能自我嵌套,且不会触发FROM和MAINTAINER指令
使用包含ONBUILD指令的Dockerfile构建的镜像应该使用特殊的标签,例如 ruby:2.0-onbuild
在ONBUILD指令中使用ADD或COPY指令应该格外小心,因为新构建过程的上下文在缺少指定的源文件时会失败
ONBUILD 在构建镜像时不会运行,是别人基于这个镜像作为基础镜像构建时,才会运行

示例:
  ONBUILD ADD . /app/src
  ONBUILD RUN /usr/local/bin/python-build --dir /app/src
注:
  当所构建的镜像被用做其它镜像的基础镜像,该镜像中的触发器将会被钥触发
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12

如下例子

###增加一个ONBUILD命令,执行RUN

FROM centos:7.3.1611
MAINTAINER "sunny <sunny@ghbsunny.cn>"
ENV nginx_ver=1.14.0
ENV nginx_url=http://nginx.org/download/nginx-${nginx_ver}.tar.gz
WORKDIR "/usr/local/src"
EXPOSE 80/tcp
ADD ${nginx_url} /usr/local/src/
RUN tar xf nginx-${nginx_ver}.tar.gz && yum -y install gcc pcre-devel openssl-devel make \
    && cd nginx-${nginx_ver} && ./configure && make && make install
CMD ["/usr/local/nginx/sbin/nginx","-g","daemon off;"]
ONBUILD RUN echo -e "\nSunny do an onbuild~\n" >> /etc/issue

###构建镜像
[root@node1 img4]# docker build -t nginx:v6 ./
基于nginx:v6启动容器,此时/etc/issue还没写入echo要插入的信息
[root@node1 img4]# docker run -it --rm -P --name nginxv3 nginx:v6 /bin/bash
[root@16e90f7a6460 src]# cat /etc/issue
\S
Kernel \r on an \m
[root@16e90f7a6460 src]#

###然后基于这个nginx:v6镜像,再次制作一个新镜像,编辑一个新的Dockerfile
[root@node1r ~]# mkdir nginxv7
[root@node1 ~]# cd nginxv7/
[root@node1 nginxv7]# vim Dockerfile
FROM nginx:v6
MAINTAINER "sunny <sunny@ghbsunny.cn>"
CMD "/bin/bash"

###构建镜像,注意,会提示执行一个build trigger,如下Executing 1 build trigger
[root@node1 nginxv7]# docker build -t nginx:v7 ./
Sending build context to Docker daemon  2.048kB
Step 1/3 : FROM nginx:v6
# Executing 1 build trigger
 ---> Running in 6bb18c52af99
Removing intermediate container 6bb18c52af99

###基于新镜像nginx:v7启动新容器nginxv7
[root@node1 nginxv7]# docker run -it --rm --name nginxv7 nginx:v7
[root@becc66948713 src]# cat /etc/issue
\S
Kernel \r on an \m
Sunny do an onbuild~
[root@becc66948713 src]#

###此时,在旧的镜像中的dockerfile里的ONBUILD已经触发,把信息写入到/etc/issue里
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48

一个例子

ROM debian:stretch-slim

# add our user and group first to make sure their IDs get assigned consistently, regardless of whatever dependencies get added
RUN groupadd -r mysql && useradd -r -g mysql mysql
RUN apt-get update && apt-get install -y --no-install-recommends gnupg dirmngr && rm -rf /var/lib/apt/lists/*

# add gosu for easy step-down from root
ENV GOSU_VERSION 1.7
RUN set -x \
	&& apt-get update && apt-get install -y --no-install-recommends ca-certificates wget && rm -rf /var/lib/apt/lists/* \
	&& wget -O /usr/local/bin/gosu "https://github.com/tianon/gosu/releases/download/$GOSU_VERSION/gosu-$(dpkg --print-architecture)" \
	&& wget -O /usr/local/bin/gosu.asc "https://github.com/tianon/gosu/releases/download/$GOSU_VERSION/gosu-$(dpkg --print-architecture).asc" \
	&& export GNUPGHOME="$(mktemp -d)" \
	&& gpg --batch --keyserver ha.pool.sks-keyservers.net --recv-keys B42F6819007F00F88E364FD4036A9C25BF357DD4 \
	&& gpg --batch --verify /usr/local/bin/gosu.asc /usr/local/bin/gosu \
	&& gpgconf --kill all \
	&& rm -rf "$GNUPGHOME" /usr/local/bin/gosu.asc \
	&& chmod +x /usr/local/bin/gosu \
	&& gosu nobody true \
	&& apt-get purge -y --auto-remove ca-certificates wget

RUN mkdir /docker-entrypoint-initdb.d
RUN apt-get update && apt-get install -y --no-install-recommends \

# for MYSQL_RANDOM_ROOT_PASSWORD
		pwgen \
# for mysql_ssl_rsa_setup
		openssl \
# FATAL ERROR: please install the following Perl modules before executing /usr/local/mysql/scripts/mysql_install_db:
# File::Basename
# File::Copy
# Sys::Hostname
# Data::Dumper
		perl \
	&& rm -rf /var/lib/apt/lists/*
RUN set -ex; \

# gpg: key 5072E1F5: public key "MySQL Release Engineering <mysql-build@oss.oracle.com>" imported
	key='A4A9406876FCBD3C456770C88C718D3B5072E1F5'; \
	export GNUPGHOME="$(mktemp -d)"; \
	gpg --batch --keyserver ha.pool.sks-keyservers.net --recv-keys "$key"; \
	gpg --batch --export "$key" > /etc/apt/trusted.gpg.d/mysql.gpg; \
	gpgconf --kill all; \
	rm -rf "$GNUPGHOME"; \
	apt-key list > /dev/null

ENV MYSQL_MAJOR 5.7
ENV MYSQL_VERSION 5.7.24-1debian9

RUN echo "deb http://repo.mysql.com/apt/debian/ stretch mysql-${MYSQL_MAJOR}" > /etc/apt/sources.list.d/mysql.list

# the "/var/lib/mysql" stuff here is because the mysql-server postinst doesn't have an explicit way to disable the mysql_install_db codepath besides having a database already "configured" (ie, stuff in /var/lib/mysql/mysql)

# also, we set debconf keys to make APT a little quieter

RUN { \
		echo mysql-community-server mysql-community-server/data-dir select ''; \
		echo mysql-community-server mysql-community-server/root-pass password ''; \
		echo mysql-community-server mysql-community-server/re-root-pass password ''; \
		echo mysql-community-server mysql-community-server/remove-test-db select false; \
	} | debconf-set-selections \
	&& apt-get update && apt-get install -y mysql-server="${MYSQL_VERSION}" && rm -rf /var/lib/apt/lists/* \
	&& rm -rf /var/lib/mysql && mkdir -p /var/lib/mysql /var/run/mysqld \
	&& chown -R mysql:mysql /var/lib/mysql /var/run/mysqld \

# ensure that /var/run/mysqld (used for socket and lock files) is writable regardless of the UID our mysqld instance ends up having at runtime
	&& chmod 777 /var/run/mysqld \

# comment out a few problematic configuration values
	&& find /etc/mysql/ -name '*.cnf' -print0 \
		| xargs -0 grep -lZE '^(bind-address|log)' \
		| xargs -rt -0 sed -Ei 's/^(bind-address|log)/#&/' \

# don't reverse lookup hostnames, they are usually another container
	&& echo '[mysqld]\nskip-host-cache\nskip-name-resolve' > /etc/mysql/conf.d/docker.cnf

VOLUME /var/lib/mysql

COPY docker-entrypoint.sh /usr/local/bin/
RUN ln -s usr/local/bin/docker-entrypoint.sh /entrypoint.sh # backwards compat
ENTRYPOINT ["docker-entrypoint.sh"]

EXPOSE 3306 33060
CMD ["mysqld"]
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63
  • 64
  • 65
  • 66
  • 67
  • 68
  • 69
  • 70
  • 71
  • 72
  • 73
  • 74
  • 75
  • 76
  • 77
  • 78
  • 79
  • 80
  • 81
  • 82
  • 83
  • 84

三、docker build 命令

docker build 命令用于使用 Dockerfile 创建镜像。

参数:

OPTIONS描述
–build-arg=[]设置镜像创建时的变量;
–cpu-shares设置 cpu 使用权重;
–cpu-period限制 CPU CFS周期;
–cpu-quota限制 CPU CFS配额;
–cpuset-cpus指定使用的CPU id;
–cpuset-mems指定使用的内存 id;
–disable-content-trust忽略校验,默认开启;
-f指定要使用的Dockerfile路径;
–force-rm设置镜像过程中删除中间容器;
–isolation使用容器隔离技术;默认–isolation=“default”,即Linux命名空间;其他还有process或hyperv
–label=[]设置镜像使用的元数据;
-m设置内存最大值;
–memory-swap设置Swap的最大值为内存+swap,"-1"表示不限swap;
–no-cache创建镜像的过程不使用缓存;默认false。设置该选项,将不使用Build Cache构建镜像
–pull尝试去更新镜像的新版本;
–quiet, -q安静模式,成功后只输出镜像 ID;
–rm设置镜像成功后删除中间容器;
–shm-size设置/dev/shm的大小,默认值是64M;
–ulimitUlimit配置。
–tag, -t镜像的名字及标签,通常 name:tag 或者 name 格式;可以在一次构建中为一个镜像设置多个标签。
–network默认 default。在构建期间设置RUN指令的网络模式
–compress默认false。设置该选项,将使用gzip压缩构建的上下文
–squash默认false。设置该选项,将新构建出的多个层压缩为一个新层,但是将无法在多个镜像之间共享新层;设置该选项,实际上是创建了新image,同时保留原有image。

PATH | URL | -说明:

给出命令执行的上下文。
上下文可以是构建执行所在的本地路径,也可以是远程URL,如Git库、tarball或文本文件等。
如果是Git库,如https://github.com/docker/rootfs.git#container:docker,则隐含先执行git clone --depth 1 --recursive,到本地临时目录;然后再将该临时目录发送给构建进程。
构建镜像的进程中,可以通过ADD命令将上下文中的任何文件(注意文件必须在上下文中)加入到镜像中。
-表示通过STDIN给出Dockerfile或上下文。
  • 1
  • 2
  • 3
  • 4
  • 5

示例

docker build -t bjc/demo:latest --rm .
  • 1

解析:-t bjc/demo:latest,为构建的镜像标记名称,即镜像名为:bjc/demo,打标为latest;--rm,整个构建过程成功后删除中间环节的容器;.,单独的点,意思为根据当前目录下的Dockerfile文件生成镜像

四、dockerfile制作原则建议

容器轻量化。从镜像中产生的容器应该尽量轻量化,能在足够短的时间内停止、销毁、重新生成并替换原来的容器。

使用 .gitignore。在大部分情况下,Dockerfile 会和构建所需的文件放在同一个目录中,为了提高构建的性能,应该使用 .gitignore 来过滤掉不需要的文件和目录。

为了减少镜像的大小,减少依赖,仅安装需要的软件包。

一个容器只做一件事。解耦复杂的应用,分成多个容器,而不是所有东西都放在一个容器内运行。如一个 Python Web 应用,可能需要 Server、DB、Cache、MQ、Log 等几个容器。一个更加极端的说法:One process per container。

减少镜像的图层。不要多个 Label、ENV 等标签。

对续行的参数按照字母表排序,特别是使用apt-get install -y安装包的时候。

使用构建缓存。如果不想使用缓存,可以在构建的时候使用参数--no-cache=true来强制重新生成中间镜像。
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13

五、使用实例

5.1、构建Nginx运行环境

# 指定基础镜像
FROM sameersbn/ubuntu:14.04.20161014
 
# 维护者信息
MAINTAINER sameer@damagehead.com
 
# 设置环境
ENV RTMP_VERSION=1.1.10 \
    NPS_VERSION=1.11.33.4 \
    LIBAV_VERSION=11.8 \
    NGINX_VERSION=1.10.1 \
    NGINX_USER=www-data \
    NGINX_SITECONF_DIR=/etc/nginx/sites-enabled \
    NGINX_LOG_DIR=/var/log/nginx \
    NGINX_TEMP_DIR=/var/lib/nginx \
    NGINX_SETUP_DIR=/var/cache/nginx
 
# 设置构建时变量,镜像建立完成后就失效
ARG BUILD_LIBAV=false
ARG WITH_DEBUG=false
ARG WITH_PAGESPEED=true
ARG WITH_RTMP=true
 
# 复制本地文件到容器目录中
COPY setup/ ${NGINX_SETUP_DIR}/
RUN bash ${NGINX_SETUP_DIR}/install.sh
 
# 复制本地配置文件到容器目录中
COPY nginx.conf /etc/nginx/nginx.conf
COPY entrypoint.sh /sbin/entrypoint.sh
 
# 运行指令
RUN chmod 755 /sbin/entrypoint.sh
 
# 允许指定的端口
EXPOSE 80/tcp 443/tcp 1935/tcp
 
# 指定网站目录挂载点
VOLUME ["${NGINX_SITECONF_DIR}"]
 
ENTRYPOINT ["/sbin/entrypoint.sh"]
CMD ["/usr/sbin/nginx"]
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42

5.2、构建tomcat 环境

# 指定基于的基础镜像
FROM ubuntu:13.10  
 
# 维护者信息
MAINTAINER zhangjiayang "zhangjiayang@sczq.com.cn"  
  
# 镜像的指令操作
# 获取APT更新的资源列表
RUN echo "deb http://archive.ubuntu.com/ubuntu precise main universe"> /etc/apt/sources.list
# 更新软件
RUN apt-get update  
  
# Install curl  
RUN apt-get -y install curl  
  
# Install JDK 7  
RUN cd /tmp &&  curl -L 'http://download.oracle.com/otn-pub/java/jdk/7u65-b17/jdk-7u65-linux-x64.tar.gz' -H 'Cookie: oraclelicense=accept-securebackup-cookie; gpw_e24=Dockerfile' | tar -xz  
RUN mkdir -p /usr/lib/jvm  
RUN mv /tmp/jdk1.7.0_65/ /usr/lib/jvm/java-7-oracle/  
  
# Set Oracle JDK 7 as default Java  
RUN update-alternatives --install /usr/bin/java java /usr/lib/jvm/java-7-oracle/bin/java 300     
RUN update-alternatives --install /usr/bin/javac javac /usr/lib/jvm/java-7-oracle/bin/javac 300     
 
# 设置系统环境
ENV JAVA_HOME /usr/lib/jvm/java-7-oracle/  
  
# Install tomcat7  
RUN cd /tmp && curl -L 'http://archive.apache.org/dist/tomcat/tomcat-7/v7.0.8/bin/apache-tomcat-7.0.8.tar.gz' | tar -xz  
RUN mv /tmp/apache-tomcat-7.0.8/ /opt/tomcat7/  
  
ENV CATALINA_HOME /opt/tomcat7  
ENV PATH $PATH:$CATALINA_HOME/bin  
 
# 复件tomcat7.sh到容器中的目录 
ADD tomcat7.sh /etc/init.d/tomcat7  
RUN chmod 755 /etc/init.d/tomcat7  
  
# Expose ports.  指定暴露的端口
EXPOSE 8080  
  
# Define default command.  
ENTRYPOINT service tomcat7 start && tail -f /opt/tomcat7/logs/catalina.out

  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44

tomcat7.sh命令文件

export JAVA_HOME=/usr/lib/jvm/java-7-oracle/  
export TOMCAT_HOME=/opt/tomcat7  

case $1 in  
start)  
  sh $TOMCAT_HOME/bin/startup.sh  
;;  
stop)  
  sh $TOMCAT_HOME/bin/shutdown.sh  
;;  
restart)  
  sh $TOMCAT_HOME/bin/shutdown.sh  
  sh $TOMCAT_HOME/bin/startup.sh  
;;  
esac  
exit 0
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16

六、参考文档

1、https://blog.csdn.net/zisefeizhu/article/details/83472190

2、https://www.cnblogs.com/panwenbin-logs/p/8007348.html

3、https://www.cnblogs.com/jsonhc/p/7767669.html

4、https://www.cnblogs.com/yfalcon/p/9044183.html

5、https://my.oschina.net/u/4335103/blog/3207559

6、https://www.runoob.com/docker/docker-build-command.html

声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/菜鸟追梦旅行/article/detail/203097
推荐阅读
相关标签
  

闽ICP备14008679号